Files
unison/ns3

815 lines
36 KiB
Plaintext
Raw Normal View History

2021-11-10 22:28:44 -03:00
#! /usr/bin/env python3
import argparse
import glob
import os
import re
import shutil
import subprocess
import sys
from utils import read_config_file
out_dir = ""
build_separator = ""
ns3_path = os.path.dirname(os.path.abspath(__file__))
lock_file = os.sep.join([ns3_path, ".lock-waf_%s_build" % sys.platform])
2021-11-10 22:28:44 -03:00
def on_off_argument(parser, option_name, help_on, help_off=None):
parser.add_argument('--enable-%s' % option_name,
help=('Enable %s' % help_on) if help_off is None else help_on,
action="store_true", default=None)
parser.add_argument('--disable-%s' % option_name,
help=('Disable %s' % help_on) if help_off is None else help_off,
action="store_true", default=None)
return parser
def on_off(condition):
return "ON" if condition else "OFF"
def on_off_condition(args, cmake_flag, option_name):
enable_option = args.__getattribute__("enable_" + option_name)
disable_option = args.__getattribute__("disable_" + option_name)
cmake_arg = None
if enable_option is not None or disable_option is not None:
cmake_arg = "-DNS3_%s=%s" % (cmake_flag, on_off(enable_option and not disable_option))
return cmake_arg
def parse_args(argv):
parser = argparse.ArgumentParser(description="ns-3 wrapper for the CMake build system")
2021-11-10 22:28:44 -03:00
sub_parser = parser.add_subparsers()
parser_configure = sub_parser.add_parser('configure',
help='Try "./ns3 configure --help" for more configuration options')
2021-11-10 22:28:44 -03:00
parser_configure.add_argument('configure',
nargs='?',
action='store', default=True)
parser_configure.add_argument('-d', '--build-profile',
help='Build profile',
dest='build_profile',
choices=["debug", "release", "optimized"],
action="store", type=str, default=None)
parser_configure.add_argument('-G',
help=('CMake generator '
'(e.g. https://cmake.org/cmake/help/latest/manual/cmake-generators.7.html)'),
action="store", type=str, default=None)
parser_configure.add_argument('--cxx-standard',
help='Compile NS-3 with the given C++ standard',
type=str, default=None)
parser_configure = on_off_argument(parser_configure, "asserts", "the asserts regardless of the compile mode")
parser_configure = on_off_argument(parser_configure, "logs", "the logs regardless of the compile mode")
parser_configure = on_off_argument(parser_configure, "tests", "the ns-3 tests")
parser_configure = on_off_argument(parser_configure, "examples", "the ns-3 examples")
parser_configure = on_off_argument(parser_configure, "static", "Build a single static library with all ns-3",
"Restore the shared libraries")
parser_configure = on_off_argument(parser_configure, "mpi", "the MPI support for distributed simulation")
parser_configure = on_off_argument(parser_configure, "des-metrics",
"Logging all events in a json file with the name of the executable "
"(which must call CommandLine::Parse(argc, argv)")
parser_configure = on_off_argument(parser_configure, "gcov", "code coverage analysis")
parser_configure = on_off_argument(parser_configure, "gtk", "GTK support in ConfigStore")
parser_configure = on_off_argument(parser_configure, "warnings", "compiler warnings")
parser_configure = on_off_argument(parser_configure, "werror", "Treat compiler warnings as errors",
"Treat compiler warnings as warnings")
parser_configure = on_off_argument(parser_configure, "documentation", "documentation targets")
2021-11-10 22:28:44 -03:00
parser_configure.add_argument('--enable-modules',
help='List of modules to build (e.g. core;network;internet)',
action="store", type=str, default=None)
parser_configure.add_argument('--disable-modules',
help='List of modules not to build (e.g. lte;wimax)',
action="store", type=str, default=None)
parser_configure.add_argument('--lcov-report',
help=('Generate a code coverage report '
'(use this option after configuring with --enable-gcov and running a program)'),
action="store_true", default=None)
parser_configure.add_argument('--lcov-zerocounters',
help=('Zero the lcov counters'
'(use this option before rerunning a program'
'when generating repeated lcov reports)'),
action="store_true", default=None)
parser_configure.add_argument('--out', '--output-directory',
help=('Use BRITE integration support, given by the indicated path,'
' to allow the use of the BRITE topology generator'),
type=str, default=None, dest="output_directory")
2021-11-10 22:28:44 -03:00
parser_configure.add_argument('--with-brite',
help=('Use BRITE integration support, given by the indicated path,'
' to allow the use of the BRITE topology generator'),
type=str, default=None)
parser_configure.add_argument('--with-click',
help='Path to Click source or installation prefix for NS-3 Click Integration support',
type=str, default=None)
parser_configure.add_argument('--with-openflow',
help='Path to OFSID source for NS-3 OpenFlow Integration support',
type=str, default=None)
parser_configure.add_argument('--force-refresh',
help='Force refresh the CMake cache by deleting'
'the cache and reconfiguring the project',
action="store_true", default=None)
parser_build = sub_parser.add_parser('build',
help='Accepts a list of targets to build, or builds the entire project if no target is given')
2021-11-10 22:28:44 -03:00
parser_build.add_argument('build',
help='Build the entire project or the specified target and dependencies',
action="store", nargs='*', default=None)
parser_clean = sub_parser.add_parser('clean', help='Removes files created by waf and ns3')
2021-11-10 22:28:44 -03:00
parser_clean.add_argument('clean',
nargs="?",
action="store", default=True)
parser.add_argument('--dry-run',
help="Do not execute the commands",
action="store_true", default=None)
parser.add_argument('-v', '--verbose',
2021-11-10 22:28:44 -03:00
help="Print underlying commands",
action="store_true", default=None)
parser.add_argument('--check-config',
help='Print the current configuration.',
action="store_true", default=None)
parser.add_argument('--cwd',
help='Set the working directory for a program.',
action="store", type=str, default=None)
parser.add_argument('--no-task-lines',
help="Don't print task lines, i.e. messages saying which tasks are being executed.",
action="store_true", default=None)
parser.add_argument('--run',
help=('Run a locally built program; argument can be a program name,'
' or a command starting with the program name.'),
type=str, default='')
parser.add_argument('--run-no-build',
help=(
'Run a locally built program without rebuilding the project; '
2021-11-10 22:28:44 -03:00
'argument can be a program name, or a command starting with the program name.'),
type=str, default='')
parser.add_argument('--visualize',
help='Modify --run arguments to enable the visualizer',
action="store_true", default=None)
parser.add_argument('--command-template',
help=('Template of the command used to run the program given by --run;'
' It should be a shell command string containing %s inside,'
' which will be replaced by the actual program.'),
type=str, default=None)
parser.add_argument('--pyrun',
help=('Run a python program using locally built ns3 python module;'
' argument is the path to the python program, optionally followed'
' by command-line options that are passed to the program.'),
type=str, default='')
parser.add_argument('--pyrun-no-build',
help=(
'Run a python program using locally built ns3 python module without rebuilding the project;'
' argument is the path to the python program, optionally followed'
' by command-line options that are passed to the program.'),
type=str, default='')
parser.add_argument('--gdb',
help='Change the default command template to run programs and unit tests with gdb',
action="store_true", default=None)
parser.add_argument('--valgrind',
help='Change the default command template to run programs and unit tests with valgrind',
action="store_true", default=None)
parser.add_argument('-j', '--jobs',
help='Set number of parallel jobs',
action='store', type=int, dest="jobs", default=max(1, os.cpu_count() - 1))
2021-11-10 22:28:44 -03:00
# parser.add_argument('--shell',
# help=('DEPRECATED (run ./waf shell)'),
# action="store_true", default=None)
# parser.add_argument('--enable-sudo',
# help=('Use sudo to setup suid bits on ns3 executables.'),
# dest='enable_sudo', action='store_true',
# default=None)
parser.add_argument('--check',
help='DEPRECATED (run ./test.py)',
action='store_true', default=None)
parser.add_argument('--doxygen',
help='Run doxygen to generate html documentation from source comments.',
action="store_true", default=None)
parser.add_argument('--doxygen-no-build',
help=('Run doxygen to generate html documentation from source comments, '
'but do not wait for ns-3 to finish the full build.'),
action="store_true", default=None)
2021-11-10 22:28:44 -03:00
# parser.add_argument('--docset',
# help=(
# 'Create Docset, without building. This requires the docsetutil tool from Xcode 9.2 or earlier.'
# 'See Bugzilla 2196 for more details.'),
# action="store_true", default=None,
# dest="docset_build")
return parser.parse_args(argv)
def check_build_profile(output_directory):
2021-11-10 22:28:44 -03:00
# Check the c4cache for the build type (in case there are multiple cmake cache folders
c4che_path = os.sep.join([output_directory, "c4che", "_cache.py"])
2021-11-10 22:28:44 -03:00
build_profile = None
ns3_version = None
ns3_modules = None
ns3_modules_tests = []
if output_directory and os.path.exists(c4che_path):
2021-11-10 22:28:44 -03:00
c4che_info = {}
exec(open(c4che_path).read(), globals(), c4che_info)
build_profile = c4che_info["BUILD_PROFILE"]
ns3_version = c4che_info["VERSION"]
ns3_modules = c4che_info["NS3_ENABLED_MODULES"]
ns3_modules_tests = [x + "-test" for x in ns3_modules]
return build_profile, ns3_version, ns3_modules + ns3_modules_tests if ns3_modules else None
2021-11-10 22:28:44 -03:00
def clean_cmake_artifacts(verbose=False, dry_run=False):
2021-11-10 22:28:44 -03:00
if verbose:
print("rm -R %s" % out_dir)
2021-11-10 22:28:44 -03:00
if not dry_run:
shutil.rmtree(out_dir, ignore_errors=True)
2021-11-10 22:28:44 -03:00
cmake_cache_files = glob.glob("%s/**/CMakeCache.txt" % ns3_path, recursive=True)
2021-11-10 22:28:44 -03:00
for cmake_cache_file in cmake_cache_files:
dirname = os.path.dirname(cmake_cache_file)
if verbose:
print("rm -R %s" % dirname)
if not dry_run:
shutil.rmtree(dirname, ignore_errors=True)
if os.path.exists(lock_file):
if verbose:
print("rm %s" % lock_file)
os.remove(lock_file)
2021-11-10 22:28:44 -03:00
def search_cmake_cache(build_profile):
2021-11-10 22:28:44 -03:00
# Search for the CMake cache
cmake_cache_files = glob.glob("%s/**/CMakeCache.txt" % ns3_path, recursive=True)
current_cmake_cache_folder = None
current_cmake_generator = None
if cmake_cache_files:
# In case there are multiple cache files, get the correct one
for cmake_cache_file in cmake_cache_files:
# We found the right cache folder
if current_cmake_cache_folder and current_cmake_generator:
break
# Still looking for it
current_cmake_cache_folder = None
current_cmake_generator = None
with open(cmake_cache_file, "r") as f:
lines = f.read().split("\n")
while len(lines):
line = lines[0]
lines.pop(0)
# Check for EOF
if current_cmake_cache_folder and current_cmake_generator:
break
# Check the build profile
if "CMAKE_BUILD_TYPE" in line:
if build_profile == line.split("=")[-1].lower():
current_cmake_cache_folder = os.path.dirname(cmake_cache_file)
# Check the generator
if "CMAKE_GENERATOR" in line:
current_cmake_generator = line.split("=")[-1]
if not current_cmake_generator:
# Search for available generators
cmake_generator_map = {"make": "Unix Makefiles",
"ninja": "Ninja",
"xcodebuild": "Xcode"
}
available_generators = []
for generator in cmake_generator_map.keys():
if shutil.which(generator):
available_generators.append(generator)
# Select the first one
if len(available_generators) == 0:
raise Exception("No generator available.")
current_cmake_generator = cmake_generator_map[available_generators[0]]
return current_cmake_cache_folder, current_cmake_generator
def check_config(current_cmake_cache_folder):
if current_cmake_cache_folder is None:
raise Exception("Project was not configured")
2021-11-10 22:28:44 -03:00
waf_like_config_table = current_cmake_cache_folder + os.sep + "ns3wafconfig.txt"
if not os.path.exists(waf_like_config_table):
raise Exception("Project was not configured")
with open(waf_like_config_table, "r") as f:
print(f.read())
def configure_cmake(cmake, args, current_cmake_cache_folder, current_cmake_generator, output, verbose=False,
dry_run=False):
2021-11-10 22:28:44 -03:00
# Aggregate all flags to configure CMake
cmake_args = [cmake]
first_run = False
if not os.path.exists(current_cmake_cache_folder + os.sep + "CMakeCache.txt"):
first_run = True
if "configure" in args:
if first_run:
# Set default build type to default if a previous cache doesn't exist
if args.build_profile is None:
args.build_profile = "debug"
# Set generator if a previous cache doesn't exist
if args.G is None:
args.G = current_cmake_generator
# C++ standard
if args.cxx_standard is not None:
cmake_args.append("-DCMAKE_CXX_STANDARD=%s" % args.cxx_standard)
# Build type
if args.build_profile is not None:
args.build_profile = args.build_profile.lower()
if args.build_profile not in ["debug", "release", "optimized"]:
raise Exception("Unknown build type")
else:
if args.build_profile == "debug":
cmake_args.append("-DCMAKE_BUILD_TYPE=debug")
else:
cmake_args.append("-DCMAKE_BUILD_TYPE=release")
cmake_args.append("-DNS3_NATIVE_OPTIMIZATIONS=%s" % on_off((args.build_profile == "optimized")))
options = (("ASSERT", "asserts"),
("LOG", "logs"),
("TESTS", "tests"),
("EXAMPLES", "examples"),
("COVERAGE", "gcov"),
("DES_METRICS", "des_metrics"),
("STATIC", "static"),
("MPI", "mpi"),
("GTK3", "gtk"),
("WARNINGS", "warnings"),
("WARNINGS_AS_ERRORS", "werror"),
("DOCS", "documentation")
2021-11-10 22:28:44 -03:00
)
for (cmake_flag, option_name) in options:
arg = on_off_condition(args, cmake_flag, option_name)
if arg:
cmake_args.append(arg)
if args.lcov_zerocounters is not None:
cmake_args.append("-DNS3_COVERAGE_ZERO_COUNTERS=%s" % on_off(args.lcov_zerocounters))
# Output, Brite, Click and Openflow directories
if args.output_directory is not None:
cmake_args.append("-DNS3_OUTPUT_DIRECTORY=%s" % args.output_directory)
2021-11-10 22:28:44 -03:00
if args.with_brite is not None:
cmake_args.append("-DNS3_WITH_BRITE=%s" % args.with_brite)
if args.with_click is not None:
cmake_args.append("-DNS3_WITH_CLICK=%s" % args.with_click)
if args.with_openflow is not None:
cmake_args.append("-DNS3_WITH_OPENFLOW=%s" % args.with_openflow)
# Build and link visualizer
if args.visualize is not None:
cmake_args.append("-DNS3_VISUALIZER=%s" % on_off(args.visualize))
# Process enabled/disabled modules
if args.enable_modules:
cmake_args.append("-DNS3_ENABLED_MODULES=%s" % args.enable_modules)
if args.disable_modules:
cmake_args.append("-DNS3_DISABLED_MODULES=%s" % args.disable_modules)
# Try to set specified generator (will probably fail if there is an old cache)
if args.G:
cmake_args.append("-G%s" % args.G)
if "configure" not in args:
if first_run:
print("You need to configure ns-3 first: try ./ns3 configure")
exit(0)
# Configure cmake
cmake_args.append("..") # for now, assuming the cmake_cache directory is inside the ns-3-dev folder
# Echo out the configure command
if verbose:
print("cd %s; %s" % (current_cmake_cache_folder, " ".join(cmake_args)))
# Run cmake
if not dry_run:
subprocess.run(cmake_args, cwd=current_cmake_cache_folder, stdout=output)
2021-11-10 22:28:44 -03:00
return first_run
def get_program_shortcuts(build_profile, ns3_version):
build_status_file = os.sep.join([out_dir, "build-status.py"])
2021-11-10 22:28:44 -03:00
# Import programs from build-status.py
programs_dict = {}
exec(open(build_status_file).read(), globals(), programs_dict)
# We can now build a map to simplify things for users (at this point we could remove versioning prefix/suffix)
ns3_program_map = {}
out_dir_name = os.path.basename(out_dir)
2021-11-10 22:28:44 -03:00
for program in programs_dict["ns3_runnable_programs"]:
if "pch_exec" in program:
continue
temp_path = program.split(out_dir_name)[-1].split(os.sep)
2021-11-10 22:28:44 -03:00
# Remove version prefix and buildtype suffix from shortcuts (or keep them too?)
temp_path[-1] = temp_path[-1].replace("-" + build_profile, "").replace("ns" + ns3_version + "-", "")
# Check if there is a .cc file for that specific program
source_file_path = os.sep.join(temp_path) + ".cc"
source_shortcut = False
if os.path.exists(ns3_path + source_file_path):
source_shortcut = True
program = program.strip()
while len(temp_path):
# Shortcuts: /src/aodv/examples/aodv can be accessed with aodv/examples/aodv, examples/aodv, aodv
shortcut_path = os.sep.join(temp_path)
ns3_program_map[shortcut_path] = program
if source_shortcut:
ns3_program_map[shortcut_path + ".cc"] = program
temp_path.pop(0)
return ns3_program_map
def cmake_build(current_cmake_cache_folder, output, jobs, target=None, verbose=False, dry_run=False):
# Check CMake version
cmake = shutil.which("cmake")
if not cmake:
raise Exception("CMake was not found")
version = re.findall("version (.*)\n", subprocess.check_output([cmake, "--version"]).decode("utf-8"))[0]
# Older CMake versions do not accept the number of jobs directly
jobs_part = ("-j %d" % jobs) if version >= "3.12.0" else ""
target_part = (" --target %s" % target) if target else ""
cmake_build_command = "cmake --build . %s%s" % (jobs_part, target_part)
2021-11-10 22:28:44 -03:00
if verbose:
print("cd %s; %s" % (current_cmake_cache_folder, cmake_build_command))
if not dry_run:
subprocess.run(cmake_build_command.split(), cwd=current_cmake_cache_folder, stdout=output)
2021-11-10 22:28:44 -03:00
def extract_cmakecache_settings(current_cmake_cache_folder):
try:
with open(current_cmake_cache_folder + os.sep + "CMakeCache.txt", "r", encoding="utf-8") as f:
contents = f.read()
except FileNotFoundError as e:
raise e
current_settings = re.findall("(NS3_.*):.*=(.*)", contents) # extract NS3 specific settings
current_settings.extend(re.findall("(CMAKE_BUILD_TYPE):.*=(.*)", contents)) # extract buildtype
current_settings.extend(re.findall("(CMAKE_GENERATOR):.*=(.*)", contents)) # extract generator
return dict(current_settings)
def reconfigure_cmake_to_force_refresh(cmake, current_cmake_cache_folder, output, verbose=False, dry_run=False):
2021-11-10 22:28:44 -03:00
import json
settings_bak_file = "settings.json"
# Extract settings or recover from the backup
if not os.path.exists(settings_bak_file):
settings = extract_cmakecache_settings(current_cmake_cache_folder)
else:
with open(settings_bak_file, "r", encoding="utf-8") as f:
settings = json.load(f)
# Delete cache folder and then recreate it
if verbose:
print("rm -R %s; mkdir %s" % current_cmake_cache_folder * 2)
if not dry_run:
shutil.rmtree(current_cmake_cache_folder)
os.mkdir(current_cmake_cache_folder)
2021-11-10 22:28:44 -03:00
# Save settings backup to prevent loss
with open(settings_bak_file, "w", encoding="utf-8") as f:
json.dump(settings, f, indent=2)
# Reconfigure CMake preserving previous NS3 settings
cmake_args = [cmake]
for setting in settings.items():
if setting[1]:
cmake_args.append("-D%s=%s" % setting)
cmake_args.append("..")
# Echo out the configure command
if verbose:
print("cd %s; %s" % (current_cmake_cache_folder, " ".join(cmake_args)))
# Call cmake
if not dry_run:
ret = subprocess.run(cmake_args, cwd=current_cmake_cache_folder, stdout=output)
2021-11-10 22:28:44 -03:00
# If it succeeds, delete backup, otherwise raise exception
if ret.returncode == 0:
os.remove(settings_bak_file)
else:
raise Exception("Reconfiguring CMake to force refresh failed. "
"A backup of the settings was saved in %s" % settings_bak_file)
2021-11-10 22:28:44 -03:00
def get_target_to_build(program_path, ns3_version, build_profile):
2021-11-10 22:28:44 -03:00
build_profile_suffix = "" if build_profile in ["release"] else "-" + build_profile
program_name = "".join(*re.findall("(.*)ns%s-(.*)%s" % (ns3_version, build_profile_suffix), program_path))
2021-11-10 22:28:44 -03:00
if "scratch" in program_path:
# Get the path to the program and replace slashes with underlines
# to get unique targets for CMake, preventing collisions with modules examples
return program_name.split(out_dir + "/")[1].replace("/", "_")
else:
# Other programs just use their normal names (without version prefix and build_profile suffix) as targets
return program_name.split("/")[-1]
2021-11-10 22:28:44 -03:00
def configuration_step(current_cmake_cache_folder, current_cmake_generator, args, run_only, configure_and_run,
output, dry_run=False):
2021-11-10 22:28:44 -03:00
# There are a few cases where we want to reconfigure/refresh the cmake cache
# MANUALLY (does require ./ns3 configure)
# 1. When we want to change settings (e.g. --enable-something or -DNS3_something=ON in CMake-land)
# AUTOMATICALLY (does not require ./ns3 configure)
# 2. When we want to add/remove source files from modules (e.g. add a new .cc file to a CMakeLists.txt)
# If we are not only running with --run-no-build or --pyrun-no-build
if not run_only or configure_and_run:
# Create a new cmake_cache folder if one does not exist
if not current_cmake_cache_folder:
current_cmake_cache_folder = os.sep.join([ns3_path, "cmake_cache"])
2021-11-10 22:28:44 -03:00
if not os.path.exists(current_cmake_cache_folder):
if args.verbose:
print("mkdir %s" % current_cmake_cache_folder)
if not dry_run:
os.mkdir(current_cmake_cache_folder)
current_cmake_cache_folder = os.path.abspath(current_cmake_cache_folder)
2021-11-10 22:28:44 -03:00
# Search for the CMake binary
cmake = shutil.which("cmake")
if not cmake:
raise Exception("CMake was not found")
# FORCE REFRESH IS ONLY TRIGGERED MANUALLY
# If --force-refresh, we load settings from the CMakeCache, delete it, then reconfigure CMake to
# force refresh cached packages/libraries that were installed/removed, without losing the current settings
if "configure" in args and args.force_refresh:
reconfigure_cmake_to_force_refresh(cmake, current_cmake_cache_folder, output, args.verbose, dry_run)
2021-11-10 22:28:44 -03:00
exit(0)
# Call cmake to configure/reconfigure/refresh the project
first_run = configure_cmake(cmake,
args,
current_cmake_cache_folder,
current_cmake_generator,
output,
args.verbose,
dry_run
2021-11-10 22:28:44 -03:00
)
# If manually configuring, we end the script earlier
if "configure" in args:
exit(0)
return first_run, current_cmake_cache_folder
return False, current_cmake_cache_folder
def build_step(args,
configure_and_run,
target_to_run,
current_cmake_cache_folder,
ns3_modules,
ns3_version,
build_profile,
output):
# There are two scenarios where we build everything: ./ns3 build and ./ns3 --check
if args.check or ("build" in args and len(args.build) == 0):
cmake_build(current_cmake_cache_folder,
jobs=args.jobs,
output=output,
verbose=args.verbose,
dry_run=args.dry_run
)
2021-11-10 22:28:44 -03:00
if "build" in args:
# We can exit early if only building
exit(0)
# If we are building specific targets, we build them one by one
if "build" in args:
non_executable_targets = ["doxygen", "doxygen-no-build"]
2021-11-10 22:28:44 -03:00
# Build targets in the list
for target in args.build:
if target in ns3_modules:
target = "lib" + target
elif target not in non_executable_targets:
target = get_target_to_build(target, ns3_version, build_profile)
cmake_build(current_cmake_cache_folder,
jobs=args.jobs,
target=target,
output=output,
verbose=args.verbose,
dry_run=args.dry_run)
2021-11-10 22:28:44 -03:00
# We can also exit earlier in this case
exit(0)
# The remaining case is when we want to build something to run
if configure_and_run:
cmake_build(current_cmake_cache_folder,
jobs=args.jobs,
target=get_target_to_build(target_to_run, ns3_version, build_profile),
2021-11-10 22:28:44 -03:00
output=output,
verbose=args.verbose,
dry_run=args.dry_run
2021-11-10 22:28:44 -03:00
)
def main():
global out_dir
2021-11-10 22:28:44 -03:00
# Parse arguments
args = parse_args(sys.argv[1:])
output = subprocess.DEVNULL if args.no_task_lines else None
# no arguments were passed, so can't possibly be reconfiguring anything, then we refresh and rebuild
if len(sys.argv) == 1:
args.build = []
# If Dry-run, do not execute the commands, only print the underlying commands from --verbose
if args.dry_run:
args.verbose = True
# Read contents from lock (output directory is important)
if os.path.exists(lock_file):
exec(open(lock_file).read(), globals())
else:
out_dir = os.sep.join([ns3_path, "build"])
2021-11-10 22:28:44 -03:00
# Clean project if needed
if "clean" in args and args.clean:
clean_cmake_artifacts(verbose=args.verbose, dry_run=args.dry_run)
2021-11-10 22:28:44 -03:00
# We end things earlier when cleaning
return
# Doxygen-no-build requires print-introspected-doxygen, but it has no explicit dependencies,
# differently from the doxygen target
if args.doxygen:
args.build = ['doxygen']
if args.doxygen_no_build:
args.build = ['doxygen-no-build']
2021-11-10 22:28:44 -03:00
# Get build profile
build_profile, ns3_version, ns3_modules = check_build_profile(out_dir)
2021-11-10 22:28:44 -03:00
# Check if running something or reconfiguring ns-3
run_only = args.run_no_build or args.pyrun_no_build
configure_and_run = args.run or args.pyrun
target_to_run = None
if not args.check and (run_only or configure_and_run):
target_to_run = max(args.run_no_build, args.pyrun_no_build, args.run, args.pyrun)
if len(target_to_run) > 0:
# While testing a weird case appeared where the target to run is between quotes,
# so we remove in case they exist
if target_to_run[0] in ["\"", "'"] and target_to_run[-1] in ["\"", "'"]:
target_to_run = target_to_run[1:-1]
2021-11-10 22:28:44 -03:00
target_to_run = target_to_run.split()
target_to_run, target_args = target_to_run[0], target_to_run[1:]
else:
raise Exception("You need to specify a program to run")
# Get current CMake cache folder and CMake generator (used when reconfiguring)
current_cmake_cache_folder, current_cmake_generator = search_cmake_cache(build_profile)
2021-11-10 22:28:44 -03:00
if args.check_config:
check_config(current_cmake_cache_folder)
# We end things earlier if only checking the current project configuration
return
first_run, current_cmake_cache_folder = configuration_step(current_cmake_cache_folder=current_cmake_cache_folder,
current_cmake_generator=current_cmake_generator,
args=args,
run_only=run_only,
configure_and_run=configure_and_run,
output=output,
dry_run=args.dry_run
2021-11-10 22:28:44 -03:00
)
# re-read contents from lock (output directory is important)
2021-11-10 22:28:44 -03:00
# re-run build profile check to get up-to-date information before loading the program shortcuts
if first_run:
exec(open(lock_file).read(), globals())
build_profile, ns3_version, ns3_modules = check_build_profile(out_dir)
2021-11-10 22:28:44 -03:00
# We could also replace the "ns3-" prefix used in c4che with the "lib" prefix currently used in cmake
ns3_modules = [module.replace("ns3-", "") for module in ns3_modules]
# Now that CMake is configured, we can look for c++ targets in build-status.py
ns3_programs = get_program_shortcuts(build_profile, ns3_version)
2021-11-10 22:28:44 -03:00
# If we have a target to run, replace shortcut with full path or raise exception
if run_only or configure_and_run:
if target_to_run in ns3_programs:
target_to_run = ns3_programs[target_to_run]
else:
raise Exception("Couldn't find the specified program: %s" % target_to_run)
if "build" in args:
complete_targets = []
for target in args.build:
complete_targets.append(ns3_programs[target] if target in ns3_programs else target)
args.build = complete_targets
del complete_targets
2021-11-10 22:28:44 -03:00
build_step(args,
configure_and_run,
target_to_run,
current_cmake_cache_folder,
ns3_modules,
ns3_version,
build_profile,
output
)
# To determine if we are being called to run-no-build a waf build,
# we can just check if the out_dir matches the base folder from the target_to_run
if out_dir.split(os.sep)[-1] == target_to_run.split(os.sep)[0]:
target_to_run = target_to_run.replace(out_dir.split(os.sep)[-1] + os.sep, "")
# Waf doesn't add version prefix and build type suffix to scratches, so we remove them
if "scratch" in target_to_run and run_only:
target_to_run = target_to_run.replace(os.path.basename(target_to_run), run_only)
target_to_run = os.sep.join([out_dir, target_to_run])
2021-11-10 22:28:44 -03:00
# If we're only trying to run the target, we need to check if it actually exists first
if (run_only or configure_and_run) and not os.path.exists(target_to_run):
raise Exception("Executable has not been built yet")
# Finally, we try to run it
if args.check or run_only or configure_and_run:
path_sep = ";" if os.name == "nt" else ":"
paths_to_add = path_sep.join([out_dir,
"%s/lib" % out_dir,
"%s/bindings/python" % out_dir,
2021-11-10 22:28:44 -03:00
])
proc_env = {"PATH": os.getenv("PATH") + path_sep + paths_to_add,
"LD_LIBRARY_PATH": paths_to_add,
"PYTHON_PATH": paths_to_add,
}
# running from ns-3-dev (ns3_path) or cwd
working_dir = args.cwd if args.cwd else ns3_path
debugging_software = []
# running valgrind?
if args.valgrind:
debugging_software.append(shutil.which("valgrind"))
# running gdb?
if args.gdb:
debugging_software.extend([shutil.which("gdb"), "--args"])
# running with visualizer?
if args.visualize:
target_args.append("--SimulatorImplementationType=ns3::VisualSimulatorImpl")
# running test.py/check?
if args.check:
target_to_run = os.sep.join([ns3_path, "test.py"])
target_args = ["--nowaf", "--jobs=%d" % args.jobs]
2021-11-10 22:28:44 -03:00
# running with command template?
if args.command_template:
commands = (args.command_template % target_to_run).split()
target_to_run = commands[0]
target_args = commands[1:] + target_args
program_arguments = [*debugging_software, target_to_run, *target_args]
if args.verbose:
exported_variables = "export "
for item in proc_env.items():
exported_variables += "=".join(item) + " "
print("cd %s; %s; %s" % (working_dir, exported_variables, " ".join(program_arguments)))
if not args.dry_run:
try:
subprocess.run(program_arguments, env=proc_env, cwd=working_dir)
except KeyboardInterrupt:
print("Process was interrupted by the user")
2021-11-10 22:28:44 -03:00
return 0
main()