8 from pathlib
import Path
10 WSCRIPT_TEMPLATE =
'''# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
15 # def configure(conf):
16 # conf.check_nonfatal(header_name='stdint.h', define_name='HAVE_STDINT_H')
19 module = bld.create_ns3_module({MODULE!r}, ['core'])
22 'helper/{MODULE}-helper.cc',
25 module_test = bld.create_ns3_module_test_library('{MODULE}')
26 module_test.source = [
27 'test/{MODULE}-test-suite.cc',
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',
35 headers = bld(features='ns3header')
36 headers.module = {MODULE!r}
39 'helper/{MODULE}-helper.h',
42 if bld.env.ENABLE_EXAMPLES:
43 bld.recurse('examples')
45 # bld.ns3_python_bindings()
51 MODEL_CC_TEMPLATE =
'''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
66 MODEL_H_TEMPLATE =
'''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
67 #ifndef {INCLUDE_GUARD}
68 #define {INCLUDE_GUARD}
76 #endif /* {INCLUDE_GUARD} */
82 HELPER_CC_TEMPLATE =
'''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
84 #include "{MODULE}-helper.h"
97 HELPER_H_TEMPLATE =
'''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
98 #ifndef {INCLUDE_GUARD}
99 #define {INCLUDE_GUARD}
101 #include "ns3/{MODULE}.h"
109 #endif /* {INCLUDE_GUARD} */
114 EXAMPLES_WSCRIPT_TEMPLATE =
'''# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
117 obj = bld.create_ns3_program('{MODULE}-example', [{MODULE!r}])
118 obj.source = '{MODULE}-example.cc'
122 EXAMPLE_CC_TEMPLATE =
'''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
124 #include "ns3/core-module.h"
125 #include "ns3/{MODULE}-helper.h"
131 main (int argc, char *argv[])
135 CommandLine cmd (__FILE__);
136 cmd.AddValue ("verbose", "Tell application to log if true", verbose);
138 cmd.Parse (argc,argv);
143 Simulator::Destroy ();
151 TEST_CC_TEMPLATE =
'''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
153 // Include a header file from your module to test.
154 #include "ns3/{MODULE}.h"
156 // An essential include is test.h
157 #include "ns3/test.h"
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
163 // This is an example TestCase.
164 class {CAPITALIZED}TestCase1 : public TestCase
167 {CAPITALIZED}TestCase1 ();
168 virtual ~{CAPITALIZED}TestCase1 ();
171 virtual void DoRun (void);
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)")
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 ()
187 // This method is the pure virtual method from class TestCase that every
188 // TestCase must implement
191 {CAPITALIZED}TestCase1::DoRun (void)
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");
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
203 class {CAPITALIZED}TestSuite : public TestSuite
206 {CAPITALIZED}TestSuite ();
209 {CAPITALIZED}TestSuite::{CAPITALIZED}TestSuite ()
210 : TestSuite ("{MODULE}", UNIT)
212 // TestDuration for TestCase can be QUICK, EXTENSIVE or TAKES_FOREVER
213 AddTestCase (new {CAPITALIZED}TestCase1, TestCase::QUICK);
216 // Do not forget to allocate an instance of this TestSuite
217 static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite;
222 DOC_RST_TEMPLATE =
'''Example Module Documentation
223 ----------------------------
225 .. include:: replace.txt
228 .. heading hierarchy:
229 ------------- Chapter
230 ************* Section (#.#)
231 ============= Subsection (#.#.#)
232 ############# Paragraph (no number)
234 This is a suggested outline for adding new module documentation to |ns3|.
235 See ``src/click/doc/click.rst`` for an example.
237 The introductory paragraph is for describing what this code is trying to
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``.
247 The source code for the new module lives in the directory ``{MODULE_DIR}``.
249 Add here a basic description of what is being modeled.
254 Briefly describe the software design of the model and how it fits into
255 the existing ns-3 architecture.
257 Scope and Limitations
258 =====================
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.
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.
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.
279 Include this subsection only if there are special build instructions or
280 platform limitations.
285 What helper API will users typically use? Describe it here.
290 What classes hold attributes, and what are the key ones worth mentioning?
295 What kind of data does the model generate? What are the key trace
296 sources? What kind of logging output can be enabled?
301 Go into further details (such as using the API outside of the helpers)
302 in additional sections, as needed.
307 What examples using this new code are available? Describe them here.
312 Add any tips for avoiding pitfalls, etc.
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.
323 artifact_path = Path(path)
326 with artifact_path.open(
"wt")
as f:
327 f.write(template.format(**kwargs))
330 path = Path(moduledir,
'wscript')
331 create_file(path, WSCRIPT_TEMPLATE, MODULE=modname)
336 modelpath = Path(moduledir,
"model")
337 modelpath.mkdir(parents=
True)
339 srcfile_path = modelpath.joinpath(modname).with_suffix(
'.cc')
340 create_file(srcfile_path, MODEL_CC_TEMPLATE, MODULE=modname)
342 hfile_path = modelpath.joinpath(modname).with_suffix(
'.h')
343 guard =
"{}_H".format(modname.replace(
'-',
'_').upper())
352 testpath = Path(moduledir,
"test")
353 testpath.mkdir(parents=
True)
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)]))
365 helperpath = Path(moduledir,
"helper")
366 helperpath.mkdir(parents=
True)
368 srcfile_path = helperpath.joinpath(modname+
'-helper').with_suffix(
'.cc')
369 create_file(srcfile_path, HELPER_CC_TEMPLATE, MODULE=modname)
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)
379 examplespath = Path(moduledir,
"examples")
380 examplespath.mkdir(parents=
True)
382 wscriptpath = Path(examplespath,
'wscript')
383 create_file(wscriptpath, EXAMPLES_WSCRIPT_TEMPLATE, MODULE=modname)
385 examplesfile_path = examplespath.joinpath(modname+
'-example').with_suffix(
'.cc')
386 create_file(examplesfile_path, EXAMPLE_CC_TEMPLATE, MODULE=modname)
392 docpath = Path(moduledir,
"doc")
393 docpath.mkdir(parents=
True)
397 mod_relpath = os.path.relpath(str(moduledir))
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)
407 modulepath = Path(modpath, modname)
409 if modulepath.exists():
410 print(
"Module {!r} already exists".format(modname), file=sys.stderr)
413 print(
"Creating module {}".format(modulepath))
415 functions = (make_wscript, make_model, make_test,
416 make_helper, make_examples, make_doc)
419 modulepath.mkdir(parents=
True)
421 success = all(func(modulepath, modname)
for func
in functions)
424 raise ValueError(
"Generating module artifacts failed")
426 except Exception
as e:
427 if modulepath.exists():
428 shutil.rmtree(modulepath)
430 print(
"Creating module {!r} failed: {}".format(modname, str(e)), file=sys.stderr)
437 description =
"""Generate scaffolding for ns-3 modules
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.
444 The following directory structure is generated under the contrib directory:
450 |-- <modname>-example.cc
453 |-- <modname>-helper.cc
454 |-- <modname>-helper.h
459 |-- <modname>-test-suite.cc
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.
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
487 epilog =
"""Examples:
489 %(prog)s contrib/module1
491 Creates a new module named module1 under the contrib directory
495 Creates a new module named module1 under the src directory
497 %(prog)s src/module1 contrib/module2, module3
499 Creates three modules, one under the src directory and two under the
502 %(prog)s --project myproject module1 module2
504 Creates two modules under contrib/myproject
506 %(prog)s --project myproject/sub_project module1 module2
508 Creates two modules under contrib/myproject/sub_project
512 formatter = argparse.RawDescriptionHelpFormatter
514 parser = argparse.ArgumentParser(description=description,
516 formatter_class=formatter)
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."))
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 "
536 args = parser.parse_args(argv[1:])
538 project = args.project
539 modnames = args.modnames
541 base_path = Path.cwd()
543 src_path = base_path.joinpath(
'src')
544 contrib_path = base_path.joinpath(
'contrib')
546 for p
in (src_path, contrib_path):
548 parser.error(
"Cannot find the directory '{}'.\nPlease run this "
549 "script from the top level of the ns3 directory".format(
557 allowedRE = re.compile(
'^(\w|-)+$')
564 project_path = Path(project)
566 if project_path.is_absolute():
568 project_path = project_path.relative_to(os.sep)
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_-].')
576 for name
in modnames:
579 name = name.strip(os.sep)
585 name_path = Path(name)
587 if len(name_path.parts) > 2:
588 print(
"Skipping {}: module name can not be a path".format(name))
592 modpath = contrib_path
594 if name_path.parts[0] ==
'src':
596 parser.error(
"{}: Cannot specify src/ in a module name when --project option is used".format(name))
601 name_path = name_path.relative_to(
'src')
603 elif name_path.parts[0] ==
'contrib':
604 modpath = contrib_path
607 name_path = name_path.relative_to(
'contrib')
612 modpath = contrib_path.joinpath(project_path)
614 modname = name_path.parts[0]
616 if not allowedRE.match(modname):
617 print(
"Skipping {}: module name may only contain the characters [a-zA-Z0-9_-]".format(modname))
620 modules.append((modpath, modname))
622 if all(
make_module(*module)
for module
in modules):
624 print(
"Successfully created new modules")
625 print(
"Run './waf configure' to include them in the build")
629 if __name__ ==
'__main__':
632 return_value = main(sys.argv)
633 except Exception
as e:
634 print(
"Exception: '{}'".format(e), file=sys.stderr)
637 sys.exit(return_value)
def make_module(modpath, modname)
def make_helper(moduledir, modname)
def make_wscript(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_doc(moduledir, modname)
def create_argument_parser()