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:
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