A Discrete-Event Network Simulator
API
create-module.py
Go to the documentation of this file.
1#! /usr/bin/env python3
2import sys
3import argparse
4import os
5import re
6import shutil
7
8from pathlib import Path
9
10CMAKELISTS_TEMPLATE = '''\
11check_include_file_cxx(stdint.h HAVE_STDINT_H)
12if(HAVE_STDINT_H)
13 add_definitions(-DHAVE_STDINT_H)
14endif()
15
16set(examples_as_tests_sources)
17if(${{ENABLE_EXAMPLES}})
18 set(examples_as_tests_sources
19 #test/{MODULE}-examples-test-suite.cc
20 )
21endif()
22
23build_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
37MODEL_CC_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
38
39#include "{MODULE}.h"
40
41namespace ns3 {{
42
43/* ... */
44
45
46}}
47
48'''
49
50
51
52MODEL_H_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
53#ifndef {INCLUDE_GUARD}
54#define {INCLUDE_GUARD}
55
56namespace ns3 {{
57
58/* ... */
59
60}}
61
62#endif /* {INCLUDE_GUARD} */
63
64'''
65
66
67
68HELPER_CC_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
69
70#include "{MODULE}-helper.h"
71
72namespace ns3 {{
73
74/* ... */
75
76
77}}
78
79'''
80
81
82
83HELPER_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
89namespace ns3 {{
90
91/* ... */
92
93}}
94
95#endif /* {INCLUDE_GUARD} */
96
97'''
98
99
100EXAMPLES_CMAKELISTS_TEMPLATE = '''\
101build_lib_example(
102 NAME {MODULE}-example
103 SOURCE_FILES {MODULE}-example.cc
104 LIBRARIES_TO_LINK ${{lib{MODULE}}}
105)
106
107'''
108
109EXAMPLE_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
114using namespace ns3;
115
116
117int
118main (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
138TEST_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
148using namespace ns3;
149
150// This is an example TestCase.
151class {CAPITALIZED}TestCase1 : public TestCase
152{{
153public:
154 {CAPITALIZED}TestCase1 ();
155 virtual ~{CAPITALIZED}TestCase1 ();
156
157private:
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//
177void
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//
190class {CAPITALIZED}TestSuite : public TestSuite
191{{
192public:
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
204static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
205
206'''
207
208
209DOC_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
220
221This is a suggested outline for adding new module documentation to |ns3|.
222See ``src/click/doc/click.rst`` for an example.
223
224The introductory paragraph is for describing what this code is trying to
225model.
226
227For consistency (italicized formatting), please use |ns3| to refer to
228ns-3 in the documentation (and likewise, |ns2| for ns-2). These macros
229are defined in the file ``replace.txt``.
230
231Model Description
232*****************
233
234The source code for the new module lives in the directory ``{MODULE_DIR}``.
235
236Add here a basic description of what is being modeled.
237
238Design
239======
240
241Briefly describe the software design of the model and how it fits into
242the existing ns-3 architecture.
243
244Scope and Limitations
245=====================
246
247What can the model do? What can it not do? Please use this section to
248describe the scope and limitations of the model.
249
250References
251==========
252
253Add academic citations here, such as if you published a paper on this
254model, or if readers should read a particular specification or other work.
255
256Usage
257*****
258
259This section is principally concerned with the usage of your model, using
260the public API. Focus first on most common usage patterns, then go
261into more advanced topics.
262
263Building New Module
264===================
265
266Include this subsection only if there are special build instructions or
267platform limitations.
268
269Helpers
270=======
271
272What helper API will users typically use? Describe it here.
273
274Attributes
275==========
276
277What classes hold attributes, and what are the key ones worth mentioning?
278
279Output
280======
281
282What kind of data does the model generate? What are the key trace
283sources? What kind of logging output can be enabled?
284
285Advanced Usage
286==============
287
288Go into further details (such as using the API outside of the helpers)
289in additional sections, as needed.
290
291Examples
292========
293
294What examples using this new code are available? Describe them here.
295
296Troubleshooting
297===============
298
299Add any tips for avoiding pitfalls, etc.
300
301Validation
302**********
303
304Describe how the model has been tested/validated. What tests run in the
305test suite? How much API and code is covered by the tests? Again,
306references to outside published work may help here.
307'''
308
309def 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
317def 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
325def 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
341def 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
354def 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
368def 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
381def 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
396def 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
429Generates the directory structure and skeleton files required for an ns-3
430module. All of the generated files are valid C/C++ and will compile successfully
431out of the box. ns3 configure must be run after creating new modules in order
432to integrate them into the ns-3 build system.
433
434The 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
453character groups: letters, numbers, -, _
454The script validates the module name and skips modules that have characters
455outside of the above groups. One exception to the naming rule is that src/
456or contrib/ may be added to the front of the module name to indicate where the
457module scaffold should be created. If the module name starts with src/, then
458the module is placed in the src directory. If the module name starts with
459contrib/, then the module is placed in the contrib directory. If the module
460name does not start with src/ or contrib/, then it defaults to contrib/.
461See the examples section for use cases.
462
463
464In some situations it can be useful to group multiple related modules under one
465directory. Use the --project option to specify a common parent directory where
466the modules should be generated. The value passed to --project is treated
467as a relative path. The path components have the same naming requirements as
468the module name: letters, numbers, -, _
469The project directory is placed under the contrib directory and any parts of the
470path that do not exist will be created. Creating projects in the src directory
471is 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
473as module names that don't start with contrib/ and are generated under the
474project 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
523def 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
619if __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()