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