From 9ddd31c6f9f3381ca64bac8cbd676d83bf135b67 Mon Sep 17 00:00:00 2001 From: "Peter D. Barnes, Jr" Date: Fri, 13 Mar 2020 17:12:57 -0700 Subject: [PATCH] More flexible create-module.py script Changed the default creation directory from src/ to contrib/ Moved create-modules.py from src/ to utils/ Added two new optional command line arguments: --project and --use-src-dir --project specifies a directory name or path under which the new modules will be created. --use-src-dir directs the script to create new modules in the src directory instead of contrib. This argument cannot be combined with --project. Updated contrib/wscript to search for modules at arbitrary depths instead of just the child directories under contrib. Assume the following directory structure: contrib/ project1/ module1/ wscript module2/ wscript project2/ sub_project1/ module3/ wscript module4/ wscript sub_project2/ module5/ wscript module6/ wscript data/ module7/ wscript waf configure will discover the following modules under contrib: project1/module1 project1/module2 project2/sub_project1/module3 project2/sub_project1/module4 project2/sub_project2/module5 project2/sub_project2/module6 module7 --- contrib/wscript | 77 ++- doc/manual/source/documentation.rst | 2 +- doc/manual/source/how-to-write-tests.rst | 2 +- doc/manual/source/new-modules.rst | 16 +- src/create-module.py | 417 --------------- utils/create-module.py | 637 +++++++++++++++++++++++ 6 files changed, 713 insertions(+), 438 deletions(-) delete mode 100755 src/create-module.py create mode 100755 utils/create-module.py diff --git a/contrib/wscript b/contrib/wscript index 8b8294f72..1d70895a3 100644 --- a/contrib/wscript +++ b/contrib/wscript @@ -1,5 +1,6 @@ ## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- from __future__ import print_function +from collections import deque import os, os.path import sys import shutil @@ -15,26 +16,67 @@ try: except NameError: from sets import Set as set # Python 2.3 fallback -all_contrib_modules = [] -for dirname in os.listdir('contrib'): - if dirname.startswith('.') or dirname == 'CVS': - continue - path = os.path.join('contrib', dirname) - if not os.path.isdir(path): - continue - if os.path.exists(os.path.join(path, 'wscript')): - all_contrib_modules.append(dirname) -all_contrib_modules.sort() +# Allow mulitple modules to live in a single directory in contrib. +# For example, a directory structure like: +# contrib/package/module1 +# /module2 +# Useful for external projects that are building interdependent modules that +# are logically packaged together. +def find_contrib_modules(ctx, log=False): + modules = [] + + entries = deque( (ctx.path, d) for d in ctx.path.listdir() ) + + while entries: + parent, entry = entries.popleft() + + if not entry or entry[0] == '.' or entry.endswith('CVS'): + continue + + node = parent.find_node(entry) + + if not node: + continue + + if node.isdir(): + #does this directory have a wscript file? + wscript_node = node.find_node('wscript') + + if wscript_node: + #found a wscript file, treat this directory as a module. + + #get the path relative to the context path + module_path = node.path_from(ctx.path) + modules.append(module_path) + + if log: + ctx.msg("Found contrib module", module_path) + else: + #maybe this directory is a project, + #add its children to the list of entries to process + entries.extend( (node, d) for d in node.listdir() ) + + return sorted(modules) def get_required_boost_libs(conf): - for module in all_contrib_modules: + for module in find_contrib_modules(conf): conf.recurse (module, name="required_boost_libs", mandatory=False) def options(opt): - for module in all_contrib_modules: + for module in find_contrib_modules(opt): opt.recurse(module, mandatory=False) def configure(conf): + all_contrib_modules = find_contrib_modules(conf, True) + + # Append blddir to the module path before recursing into modules + # This is required for contrib modules with test suites + blddir = os.path.abspath(os.path.join(conf.bldnode.abspath(), conf.variant)) + conf.env.append_value('NS3_MODULE_PATH', blddir) + + # Remove duplicate path items + conf.env['NS3_MODULE_PATH'] = wutils.uniquify_list(conf.env['NS3_MODULE_PATH']) + for module in all_contrib_modules: conf.recurse(module, mandatory=False) @@ -52,8 +94,11 @@ def create_ns3_module(bld, name, dependencies=(), test=False): module = bld(features='cxx cxxstlib ns3module') else: module = bld(features='cxx cxxshlib ns3module') - module.target = '%s/lib/ns%s-%s%s' % (bld.srcnode.path_from(module.path), wutils.VERSION, - name, bld.env.BUILD_SUFFIX) + target = '%s/lib/ns%s-%s%s' % (bld.srcnode.path_from(module.path), + wutils.VERSION, + name, bld.env.BUILD_SUFFIX) + + module.target = target linkflags = [] cxxflags = [] ccflags = [] @@ -153,7 +198,7 @@ def ns3_python_bindings(bld): return if ("ns3-%s" % (module,)) not in env.NS3_ENABLED_MODULES: - #print "bindings for module %s which is not enabled, skip" % module + #print "bindings for module %s which is not enabled, skip" % module) return env.append_value('PYTHON_MODULES_BUILT', module) @@ -260,6 +305,8 @@ def build(bld): bld.create_obj = types.MethodType(create_obj, bld) bld.ns3_python_bindings = types.MethodType(ns3_python_bindings, bld) + all_contrib_modules = find_contrib_modules(bld) + # Remove these modules from the list of all modules. for not_built in bld.env['MODULES_NOT_BUILT']: diff --git a/doc/manual/source/documentation.rst b/doc/manual/source/documentation.rst index 78890a755..28f44e941 100644 --- a/doc/manual/source/documentation.rst +++ b/doc/manual/source/documentation.rst @@ -56,7 +56,7 @@ Where? Documentation for a specific module, ``foo``, should normally go in ``src/foo/doc/``. For example ``src/foo/doc/foo.rst`` would be the -top-level document for the module. The ``src/create-module.py`` script +top-level document for the module. The ``utils/create-module.py`` script will create this file for you. Some models require several ``.rst`` files, and figures; these should diff --git a/doc/manual/source/how-to-write-tests.rst b/doc/manual/source/how-to-write-tests.rst index 3becadbcb..2f93b5713 100644 --- a/doc/manual/source/how-to-write-tests.rst +++ b/doc/manual/source/how-to-write-tests.rst @@ -33,7 +33,7 @@ TestSuite), these things need to be decided up front: separately in src/test/ directory). You will have to edit the wscript file in that directory to compile your new code, if it is a new file. -A program called ``src/create-module.py`` is a good starting point. +A program called ``utils/create-module.py`` is a good starting point. This program can be invoked such as ``create-module.py router`` for a hypothetical new module called ``router``. Once you do this, you will see a ``router`` directory, and a ``test/router-test-suite.cc`` diff --git a/doc/manual/source/new-modules.rst b/doc/manual/source/new-modules.rst index d765016ca..ace02c6cb 100644 --- a/doc/manual/source/new-modules.rst +++ b/doc/manual/source/new-modules.rst @@ -43,17 +43,25 @@ Not all directories will be present in each module. Step 1 - Create a Module Skeleton ********************************* -A python program is provided in the source directory that +A python program is provided in the ``utils`` directory that will create a skeleton for a new module. For the purposes of this discussion we will assume that your new module -is called ``new-module``. From the ``src`` directory, do the following +is called ``new-module``. From the top directory, do the following to create the new module: .. sourcecode:: bash - $ ./create-module.py new-module + $ ./utils/create-module.py new-module -Next, ``cd`` into ``new-module``; you will find this directory layout: +By default ``create-module.py`` creates the module skeleton in the +``src`` directory. However, it can also create modules in ``contrib``: + +.. sourcecode:: bash + + $ ./utils/create-module.py contrib/new-contrib + +Let's assume we've created our new module in ``src``. +``cd`` into ``src/new-module``; you will find this directory layout: .. sourcecode:: text diff --git a/src/create-module.py b/src/create-module.py deleted file mode 100755 index f7f469739..000000000 --- a/src/create-module.py +++ /dev/null @@ -1,417 +0,0 @@ -#! /usr/bin/env python3 -from __future__ import print_function -import sys -from optparse import OptionParser -import os - - -WSCRIPT_TEMPLATE = '''# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- - -# def options(opt): -# pass - -# def configure(conf): -# conf.check_nonfatal(header_name='stdint.h', define_name='HAVE_STDINT_H') - -def build(bld): - module = bld.create_ns3_module(%(MODULE)r, ['core']) - module.source = [ - 'model/%(MODULE)s.cc', - 'helper/%(MODULE)s-helper.cc', - ] - - module_test = bld.create_ns3_module_test_library('%(MODULE)s') - module_test.source = [ - 'test/%(MODULE)s-test-suite.cc', - ] - - headers = bld(features='ns3header') - headers.module = %(MODULE)r - headers.source = [ - 'model/%(MODULE)s.h', - 'helper/%(MODULE)s-helper.h', - ] - - if bld.env.ENABLE_EXAMPLES: - bld.recurse('examples') - - # bld.ns3_python_bindings() - -''' - - - -MODEL_CC_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ - -#include "%(MODULE)s.h" - -namespace ns3 { - -/* ... */ - - -} - -''' - - - -MODEL_H_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -#ifndef %(INCLUDE_GUARD)s -#define %(INCLUDE_GUARD)s - -namespace ns3 { - -/* ... */ - -} - -#endif /* %(INCLUDE_GUARD)s */ - -''' - - - -HELPER_CC_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ - -#include "%(MODULE)s-helper.h" - -namespace ns3 { - -/* ... */ - - -} - -''' - - - -HELPER_H_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -#ifndef %(INCLUDE_GUARD)s -#define %(INCLUDE_GUARD)s - -#include "ns3/%(MODULE)s.h" - -namespace ns3 { - -/* ... */ - -} - -#endif /* %(INCLUDE_GUARD)s */ - -''' - - -EXAMPLES_WSCRIPT_TEMPLATE = '''# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- - -def build(bld): - obj = bld.create_ns3_program('%(MODULE)s-example', [%(MODULE)r]) - obj.source = '%(MODULE)s-example.cc' - -''' - -EXAMPLE_CC_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ - -#include "ns3/core-module.h" -#include "ns3/%(MODULE)s-helper.h" - -using namespace ns3; - - -int -main (int argc, char *argv[]) -{ - bool verbose = true; - - CommandLine cmd (__FILE__); - cmd.AddValue ("verbose", "Tell application to log if true", verbose); - - cmd.Parse (argc,argv); - - /* ... */ - - Simulator::Run (); - Simulator::Destroy (); - return 0; -} - - -''' - - -TEST_CC_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ - -// Include a header file from your module to test. -#include "ns3/%(MODULE)s.h" - -// An essential include is test.h -#include "ns3/test.h" - -// Do not put your test classes in namespace ns3. You may find it useful -// to use the using directive to access the ns3 namespace directly -using namespace ns3; - -// This is an example TestCase. -class %(CAPITALIZED)sTestCase1 : public TestCase -{ -public: - %(CAPITALIZED)sTestCase1 (); - virtual ~%(CAPITALIZED)sTestCase1 (); - -private: - virtual void DoRun (void); -}; - -// Add some help text to this case to describe what it is intended to test -%(CAPITALIZED)sTestCase1::%(CAPITALIZED)sTestCase1 () - : TestCase ("%(CAPITALIZED)s test case (does nothing)") -{ -} - -// This destructor does nothing but we include it as a reminder that -// the test case should clean up after itself -%(CAPITALIZED)sTestCase1::~%(CAPITALIZED)sTestCase1 () -{ -} - -// -// This method is the pure virtual method from class TestCase that every -// TestCase must implement -// -void -%(CAPITALIZED)sTestCase1::DoRun (void) -{ - // A wide variety of test macros are available in src/core/test.h - NS_TEST_ASSERT_MSG_EQ (true, true, "true doesn't equal true for some reason"); - // Use this one for floating point comparisons - NS_TEST_ASSERT_MSG_EQ_TOL (0.01, 0.01, 0.001, "Numbers are not equal within tolerance"); -} - -// The TestSuite class names the TestSuite, identifies what type of TestSuite, -// and enables the TestCases to be run. Typically, only the constructor for -// this class must be defined -// -class %(CAPITALIZED)sTestSuite : public TestSuite -{ -public: - %(CAPITALIZED)sTestSuite (); -}; - -%(CAPITALIZED)sTestSuite::%(CAPITALIZED)sTestSuite () - : TestSuite ("%(MODULE)s", UNIT) -{ - // TestDuration for TestCase can be QUICK, EXTENSIVE or TAKES_FOREVER - AddTestCase (new %(CAPITALIZED)sTestCase1, TestCase::QUICK); -} - -// Do not forget to allocate an instance of this TestSuite -static %(CAPITALIZED)sTestSuite %(COMPOUND)sTestSuite; - -''' - - -DOC_RST_TEMPLATE = '''Example Module Documentation ----------------------------- - -.. include:: replace.txt -.. highlight:: cpp - -.. heading hierarchy: - ------------- Chapter - ************* Section (#.#) - ============= Subsection (#.#.#) - ############# Paragraph (no number) - -This is a suggested outline for adding new module documentation to |ns3|. -See ``src/click/doc/click.rst`` for an example. - -The introductory paragraph is for describing what this code is trying to -model. - -For consistency (italicized formatting), please use |ns3| to refer to -ns-3 in the documentation (and likewise, |ns2| for ns-2). These macros -are defined in the file ``replace.txt``. - -Model Description -***************** - -The source code for the new module lives in the directory ``src/%(MODULE)s``. - -Add here a basic description of what is being modeled. - -Design -====== - -Briefly describe the software design of the model and how it fits into -the existing ns-3 architecture. - -Scope and Limitations -===================== - -What can the model do? What can it not do? Please use this section to -describe the scope and limitations of the model. - -References -========== - -Add academic citations here, such as if you published a paper on this -model, or if readers should read a particular specification or other work. - -Usage -***** - -This section is principally concerned with the usage of your model, using -the public API. Focus first on most common usage patterns, then go -into more advanced topics. - -Building New Module -=================== - -Include this subsection only if there are special build instructions or -platform limitations. - -Helpers -======= - -What helper API will users typically use? Describe it here. - -Attributes -========== - -What classes hold attributes, and what are the key ones worth mentioning? - -Output -====== - -What kind of data does the model generate? What are the key trace -sources? What kind of logging output can be enabled? - -Advanced Usage -============== - -Go into further details (such as using the API outside of the helpers) -in additional sections, as needed. - -Examples -======== - -What examples using this new code are available? Describe them here. - -Troubleshooting -=============== - -Add any tips for avoiding pitfalls, etc. - -Validation -********** - -Describe how the model has been tested/validated. What tests run in the -test suite? How much API and code is covered by the tests? Again, -references to outside published work may help here. -''' - - -def main(argv): - parser = OptionParser(usage=("Usage: %prog [options] modulename\n" - "Utility script to create a basic template for a new ns-3 module")) - (options, args) = parser.parse_args() - if len(args) != 1: - parser.print_help() - return 1 - - modname = args[0].lower() - if False in [word.isalnum() for word in modname.split("-")]: - print("Module name should only contain alphanumeric characters and dashes", file=sys.stderr) - return 2 - assert os.path.sep not in modname - - moduledir = os.path.join(os.path.dirname(__file__), modname) - - if os.path.exists(moduledir): - print("Module %r already exists" % (modname,), file=sys.stderr) - return 2 - - print("Creating module %r, " - "run './waf configure' to include it in the build" % (modname,)) - - os.mkdir(moduledir) - wscript = open(os.path.join(moduledir, "wscript"), "wt") - wscript.write(WSCRIPT_TEMPLATE % dict(MODULE=modname)) - wscript.close() - - - # - # model - # - modeldir = os.path.join(moduledir, "model") - os.mkdir(modeldir) - - model_cc = open(os.path.join(moduledir, "model", "%s.cc" % modname), "wt") - model_cc.write(MODEL_CC_TEMPLATE % dict(MODULE=modname)) - model_cc.close() - - model_h = open(os.path.join(moduledir, "model", "%s.h" % modname), "wt") - model_h.write(MODEL_H_TEMPLATE % dict(MODULE=modname, INCLUDE_GUARD="%s_H" % (modname.replace("-", "_").upper()),)) - model_h.close() - - - - # - # test - # - testdir = os.path.join(moduledir, "test") - os.mkdir(testdir) - test_cc = open(os.path.join(moduledir, "test", "%s-test-suite.cc" % modname), "wt") - test_cc.write(TEST_CC_TEMPLATE % dict(MODULE=modname, - CAPITALIZED=''.join([word.capitalize() for word in modname.split('-')]), - COMPOUND=''.join([modname.split('-')[0]] + [word.capitalize() for word in modname.split('-')[1:]]), - )) - test_cc.close() - - - - # - # helper - # - helperdir = os.path.join(moduledir, "helper") - os.mkdir(helperdir) - - helper_cc = open(os.path.join(moduledir, "helper", "%s-helper.cc" % modname), "wt") - helper_cc.write(HELPER_CC_TEMPLATE % dict(MODULE=modname)) - helper_cc.close() - - helper_h = open(os.path.join(moduledir, "helper", "%s-helper.h" % modname), "wt") - helper_h.write(HELPER_H_TEMPLATE % dict(MODULE=modname, INCLUDE_GUARD="%s_HELPER_H" % (modname.replace("-", "_").upper()),)) - helper_h.close() - - # - # examples - # - examplesdir = os.path.join(moduledir, "examples") - os.mkdir(examplesdir) - - examples_wscript = open(os.path.join(examplesdir, "wscript"), "wt") - examples_wscript.write(EXAMPLES_WSCRIPT_TEMPLATE % dict(MODULE=modname)) - examples_wscript.close() - - example_cc = open(os.path.join(moduledir, "examples", "%s-example.cc" % modname), "wt") - example_cc.write(EXAMPLE_CC_TEMPLATE % dict(MODULE=modname)) - example_cc.close() - - # - # doc - # - docdir = os.path.join(moduledir, "doc") - os.mkdir(docdir) - - doc_rst = open(os.path.join(moduledir, "doc", "%s.rst" % modname), "wt") - doc_rst.write(DOC_RST_TEMPLATE % dict(MODULE=modname)) - doc_rst.close() - - - return 0 - -if __name__ == '__main__': - sys.exit(main(sys.argv)) diff --git a/utils/create-module.py b/utils/create-module.py new file mode 100755 index 000000000..385061228 --- /dev/null +++ b/utils/create-module.py @@ -0,0 +1,637 @@ +#! /usr/bin/env python3 +import sys +import argparse +import os +import re +import shutil + +from pathlib import Path + +WSCRIPT_TEMPLATE = '''# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- + +# def options(opt): +# pass + +# def configure(conf): +# conf.check_nonfatal(header_name='stdint.h', define_name='HAVE_STDINT_H') + +def build(bld): + module = bld.create_ns3_module({MODULE!r}, ['core']) + module.source = [ + 'model/{MODULE}.cc', + 'helper/{MODULE}-helper.cc', + ] + + module_test = bld.create_ns3_module_test_library('{MODULE}') + module_test.source = [ + 'test/{MODULE}-test-suite.cc', + ] + + headers = bld(features='ns3header') + headers.module = {MODULE!r} + headers.source = [ + 'model/{MODULE}.h', + 'helper/{MODULE}-helper.h', + ] + + if bld.env.ENABLE_EXAMPLES: + bld.recurse('examples') + + # bld.ns3_python_bindings() + +''' + + + +MODEL_CC_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ + +#include "{MODULE}.h" + +namespace ns3 {{ + +/* ... */ + + +}} + +''' + + + +MODEL_H_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +#ifndef {INCLUDE_GUARD} +#define {INCLUDE_GUARD} + +namespace ns3 {{ + +/* ... */ + +}} + +#endif /* {INCLUDE_GUARD} */ + +''' + + + +HELPER_CC_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ + +#include "{MODULE}-helper.h" + +namespace ns3 {{ + +/* ... */ + + +}} + +''' + + + +HELPER_H_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +#ifndef {INCLUDE_GUARD} +#define {INCLUDE_GUARD} + +#include "ns3/{MODULE}.h" + +namespace ns3 {{ + +/* ... */ + +}} + +#endif /* {INCLUDE_GUARD} */ + +''' + + +EXAMPLES_WSCRIPT_TEMPLATE = '''# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- + +def build(bld): + obj = bld.create_ns3_program('{MODULE}-example', [{MODULE!r}]) + obj.source = '{MODULE}-example.cc' + +''' + +EXAMPLE_CC_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ + +#include "ns3/core-module.h" +#include "ns3/{MODULE}-helper.h" + +using namespace ns3; + + +int +main (int argc, char *argv[]) +{{ + bool verbose = true; + + CommandLine cmd; + cmd.AddValue ("verbose", "Tell application to log if true", verbose); + + cmd.Parse (argc,argv); + + /* ... */ + + Simulator::Run (); + Simulator::Destroy (); + return 0; +}} + + +''' + + +TEST_CC_TEMPLATE = '''/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ + +// Include a header file from your module to test. +#include "ns3/{MODULE}.h" + +// An essential include is test.h +#include "ns3/test.h" + +// Do not put your test classes in namespace ns3. You may find it useful +// to use the using directive to access the ns3 namespace directly +using namespace ns3; + +// This is an example TestCase. +class {CAPITALIZED}TestCase1 : public TestCase +{{ +public: + {CAPITALIZED}TestCase1 (); + virtual ~{CAPITALIZED}TestCase1 (); + +private: + virtual void DoRun (void); +}}; + +// Add some help text to this case to describe what it is intended to test +{CAPITALIZED}TestCase1::{CAPITALIZED}TestCase1 () + : TestCase ("{CAPITALIZED} test case (does nothing)") +{{ +}} + +// This destructor does nothing but we include it as a reminder that +// the test case should clean up after itself +{CAPITALIZED}TestCase1::~{CAPITALIZED}TestCase1 () +{{ +}} + +// +// This method is the pure virtual method from class TestCase that every +// TestCase must implement +// +void +{CAPITALIZED}TestCase1::DoRun (void) +{{ + // A wide variety of test macros are available in src/core/test.h + NS_TEST_ASSERT_MSG_EQ (true, true, "true doesn\'t equal true for some reason"); + // Use this one for floating point comparisons + NS_TEST_ASSERT_MSG_EQ_TOL (0.01, 0.01, 0.001, "Numbers are not equal within tolerance"); +}} + +// The TestSuite class names the TestSuite, identifies what type of TestSuite, +// and enables the TestCases to be run. Typically, only the constructor for +// this class must be defined +// +class {CAPITALIZED}TestSuite : public TestSuite +{{ +public: + {CAPITALIZED}TestSuite (); +}}; + +{CAPITALIZED}TestSuite::{CAPITALIZED}TestSuite () + : TestSuite ("{MODULE}", UNIT) +{{ + // TestDuration for TestCase can be QUICK, EXTENSIVE or TAKES_FOREVER + AddTestCase (new {CAPITALIZED}TestCase1, TestCase::QUICK); +}} + +// Do not forget to allocate an instance of this TestSuite +static {CAPITALIZED}TestSuite s{COMPOUND}TestSuite; + +''' + + +DOC_RST_TEMPLATE = '''Example Module Documentation +---------------------------- + +.. include:: replace.txt +.. highlight:: cpp + +.. heading hierarchy: + ------------- Chapter + ************* Section (#.#) + ============= Subsection (#.#.#) + ############# Paragraph (no number) + +This is a suggested outline for adding new module documentation to |ns3|. +See ``src/click/doc/click.rst`` for an example. + +The introductory paragraph is for describing what this code is trying to +model. + +For consistency (italicized formatting), please use |ns3| to refer to +ns-3 in the documentation (and likewise, |ns2| for ns-2). These macros +are defined in the file ``replace.txt``. + +Model Description +***************** + +The source code for the new module lives in the directory ``{MODULE_DIR}``. + +Add here a basic description of what is being modeled. + +Design +====== + +Briefly describe the software design of the model and how it fits into +the existing ns-3 architecture. + +Scope and Limitations +===================== + +What can the model do? What can it not do? Please use this section to +describe the scope and limitations of the model. + +References +========== + +Add academic citations here, such as if you published a paper on this +model, or if readers should read a particular specification or other work. + +Usage +***** + +This section is principally concerned with the usage of your model, using +the public API. Focus first on most common usage patterns, then go +into more advanced topics. + +Building New Module +=================== + +Include this subsection only if there are special build instructions or +platform limitations. + +Helpers +======= + +What helper API will users typically use? Describe it here. + +Attributes +========== + +What classes hold attributes, and what are the key ones worth mentioning? + +Output +====== + +What kind of data does the model generate? What are the key trace +sources? What kind of logging output can be enabled? + +Advanced Usage +============== + +Go into further details (such as using the API outside of the helpers) +in additional sections, as needed. + +Examples +======== + +What examples using this new code are available? Describe them here. + +Troubleshooting +=============== + +Add any tips for avoiding pitfalls, etc. + +Validation +********** + +Describe how the model has been tested/validated. What tests run in the +test suite? How much API and code is covered by the tests? Again, +references to outside published work may help here. +''' + +def create_file(path, template, **kwargs): + artifact_path = Path(path) + + #open file for (w)rite and in (t)ext mode + with artifact_path.open("wt") as f: + f.write(template.format(**kwargs)) + +def make_wscript(moduledir, modname): + path = Path(moduledir, 'wscript') + create_file(path, WSCRIPT_TEMPLATE, MODULE=modname) + + return True + +def make_model(moduledir, modname): + modelpath = Path(moduledir, "model") + modelpath.mkdir(parents=True) + + srcfile_path = modelpath.joinpath(modname).with_suffix('.cc') + create_file(srcfile_path, MODEL_CC_TEMPLATE, MODULE=modname) + + hfile_path = modelpath.joinpath(modname).with_suffix('.h') + guard = "{}_H".format(modname.replace('-', '_').upper()) + create_file(hfile_path, MODEL_H_TEMPLATE, + MODULE=modname, + INCLUDE_GUARD=guard) + + return True + + +def make_test(moduledir, modname): + testpath = Path(moduledir, "test") + testpath.mkdir(parents=True) + + file_path = testpath.joinpath(modname+'-test-suite').with_suffix('.cc') + name_parts = modname.split('-') + create_file(file_path, TEST_CC_TEMPLATE, MODULE=modname, + CAPITALIZED=''.join([word.capitalize() for word in name_parts]), + COMPOUND=''.join([word.capitalize() if index > 0 else word for index, word in enumerate(name_parts)])) + + return True + + +def make_helper(moduledir, modname): + helperdir = os.path.join(moduledir, "helper") + os.mkdir(helperdir) + + src_file_name = "{}-helper.cc".format(modname) + src_file_path = os.path.join(helperdir, src_file_name) + create_file(src_file_path, HELPER_CC_TEMPLATE, MODULE=modname) + + header_file_name = "{}-helper.h".format(modname) + header_file_path = os.path.join(helperdir, header_file_name) + guard = '{}_HELPER_H'.format(modname.replace('-', '_').upper()) + create_file(header_file_path, HELPER_H_TEMPLATE, + MODULE=modname, + INCLUDE_GUARD=guard) + + return True + + +def make_examples(moduledir, modname): + examplesdir = os.path.join(moduledir, "examples") + os.mkdir(examplesdir) + + wscript_path = os.path.join(examplesdir, 'wscript') + create_file(wscript_path, EXAMPLES_WSCRIPT_TEMPLATE, MODULE=modname) + + file_name = '{}-example.cc'.format(modname) + file_path = os.path.join(examplesdir, file_name) + create_file(file_path, EXAMPLE_CC_TEMPLATE, MODULE=modname) + + return True + + +def make_doc(moduledir, modname): + docdir = os.path.join(moduledir, "doc") + os.mkdir(docdir) + + #the module_dir template parameter must be a relative path + #instead of an absolute path + mod_relpath = os.path.relpath(moduledir) + + file_name = '{}.rst'.format(modname) + file_path = os.path.join(docdir, file_name) + create_file(file_path, DOC_RST_TEMPLATE, MODULE=modname, + MODULE_DIR=mod_relpath) + + return True + +def make_module(modpath, modname): + modulepath = Path(modpath, modname) + + if modulepath.exists(): + print("Module {!r} already exists".format(modname), file=sys.stderr) + return False + + print("Creating module {}".format(modulepath)) + + functions = (make_wscript, make_model, make_test, + make_helper, make_examples, make_doc) + + try: + modulepath.mkdir(parents=True) + + success = all(func(modulepath, modname) for func in functions) + + if not success: + raise ValueError("Generating module artifacts failed") + + except Exception as e: + if modulepath.exists(): + shutil.rmtree(modulepath) + + print("Creating module {!r} failed: {}".format(modname, str(e)), file=sys.stderr) + + return False + + return True + +def create_argument_parser(): + description = """Generate scaffolding for ns-3 modules + +Generates the directory structure and skeleton files required for an ns-3 +module. All of the generated files are valid C/C++ and will compile successfully +out of the box. waf configure must be run after creating new modules in order +to integrate them into the ns-3 build system. + +The following directory structure is generated under the contrib directory: + + |-- wscript + |-- doc + |-- .rst + |-- examples + |-- -example.cc + |-- wscript + |-- helper + |-- -helper.cc + |-- -helper.h + |-- model + |-- .cc + |-- .h + |-- test + |-- -test-suite.cc + + + is the name of the module and is restricted to the following +character groups: letters, numbers, -, _ +The script validates the module name and skips modules that have characters +outside of the above groups. One exception to the naming rule is that src/ +or contrib/ may be added to the front of the module name to indicate where the +module scaffold should be created. If the module name starts with src/, then +the module is placed in the src directory. If the module name starts with +contrib/, then the module is placed in the contrib directory. If the module +name does not start with src/ or contrib/, then it defaults to contrib/. +See the examples section for use cases. + + +In some situations it can be useful to group multiple related modules under one +directory. Use the --project option to specify a common parent directory where +the modules should be generated. The value passed to --project is treated +as a relative path. The path components have the same naming requirements as +the module name: letters, numbers, -, _ +The project directory is placed under the contrib directory and any parts of the +path that do not exist will be created. Creating projects in the src directory +is not supported. Module names that start with src/ are not allowed when +--project is used. Module names that start with contrib/ are treated the same +as module names that don't start with contrib/ and are generated under the +project directory. +""" + + epilog = """Examples: + %(prog)s module1 + %(prog)s contrib/module1 + + Creates a new module named module1 under the contrib directory + + %(prog)s src/module1 + + Creates a new module named module1 under the src directory + + %(prog)s src/module1 contrib/module2, module3 + + Creates three modules, one under the src directory and two under the + contrib directory + + %(prog)s --project myproject module1 module2 + + Creates two modules under contrib/myproject + + %(prog)s --project myproject/sub_project module1 module2 + + Creates two modules under contrib/myproject/sub_project + +""" + + formatter = argparse.RawDescriptionHelpFormatter + + parser = argparse.ArgumentParser(description=description, + epilog=epilog, + formatter_class=formatter) + + parser.add_argument('--project', default='', + help=("Specify a relative path under the contrib directory " + "where the new modules will be generated. The path " + "will be created if it does not exist.")) + + parser.add_argument('modnames', nargs='+', + help=("One or more modules to generate. Module names " + "are limited to the following: letters, numbers, -, " + "_. Modules are generated under the contrib directory " + "except when the module name starts with src/. Modules " + "that start with src/ are generated under the src " + "directory.")) + + return parser + +def main(argv): + parser = create_argument_parser() + + args = parser.parse_args(argv[1:]) + + project = args.project + modnames = args.modnames + + base_path = Path.cwd() + + src_path = base_path.joinpath('src') + contrib_path = base_path.joinpath('contrib') + + for p in (src_path, contrib_path): + if not p.is_dir(): + parser.error("Cannot find the directory '{}'.\nPlease run this " + "script from the top level of the ns3 directory".format( + p)) + + # + # Error check the arguments + # + + # Alphanumeric and '-' only + allowedRE = re.compile('^(\w|-)+$') + + project_path = None + + if project: + #project may be a path in the form a/b/c + #remove any leading or trailing path separators + project_path = Path(project) + + if project_path.is_absolute(): + #remove leading separator + project_path = project_path.relative_to(os.sep) + + if not all(allowedRE.match(part) for part in project_path.parts): + parser.error('Project path may only contain the characters [a-zA-Z0-9_-].') + # + # Create each module, if it doesn't exist + # + modules = [] + for name in modnames: + if name: + #remove any leading or trailing directory separators + name = name.strip(os.sep) + + if not name: + #skip empty modules + continue + + name_path = Path(name) + + if len(name_path.parts) > 2: + print("Skipping {}: module name can not be a path".format(name)) + continue + + #default target directory is contrib + modpath = contrib_path + + if name_path.parts[0] == 'src': + if project: + parser.error("{}: Cannot specify src/ in a module name when --project option is used".format(name)) + + modpath = src_path + + #create a new path without the src part + name_path = name_path.relative_to('src') + + elif name_path.parts[0] == 'contrib': + modpath = contrib_path + + #create a new path without the contrib part + name_path = name_path.relative_to('contrib') + + if project_path: + #if a project path was specified, that overrides other paths + #project paths are always relative to the contrib path + modpath = contrib_path.joinpath(project_path) + + modname = name_path.parts[0] + + if not allowedRE.match(modname): + print("Skipping {}: module name may only contain the characters [a-zA-Z0-9_-]".format(modname)) + continue + + modules.append((modpath, modname)) + + if all(make_module(*module) for module in modules): + print() + print("Successfully created new modules") + print("Run './waf configure' to include them in the build") + + return 0 + +if __name__ == '__main__': + return_value = 0 + try: + return_value = main(sys.argv) + except Exception as e: + print("Exception: '{}'".format(e), file=sys.stderr) + return_value = 1 + + sys.exit(return_value)