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
This commit is contained in:
@@ -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']:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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``
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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))
|
||||
637
utils/create-module.py
Executable file
637
utils/create-module.py
Executable file
@@ -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:
|
||||
<modname>
|
||||
|-- wscript
|
||||
|-- doc
|
||||
|-- <modname>.rst
|
||||
|-- examples
|
||||
|-- <modname>-example.cc
|
||||
|-- wscript
|
||||
|-- helper
|
||||
|-- <modname>-helper.cc
|
||||
|-- <modname>-helper.h
|
||||
|-- model
|
||||
|-- <modname>.cc
|
||||
|-- <modname>.h
|
||||
|-- test
|
||||
|-- <modname>-test-suite.cc
|
||||
|
||||
|
||||
<modname> 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)
|
||||
Reference in New Issue
Block a user