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