From 32e95f822870de10b39d36f18abe99eda30b7627 Mon Sep 17 00:00:00 2001 From: Mathew Bielejeski Date: Tue, 11 Aug 2020 20:20:05 +0000 Subject: [PATCH] Make build information accessible by programs by extracting data from the local git repository (or a standalone file if a git repository is not present). Changes * Adds a new class named Version to the core module * Adds a template file to the core module named version-defines.h.in * Adds --PrintVersion and --version arguments to the CommandLine class. * Creates a new waf tool which queries the local git repository and extracts build information from itCreate a waf task to extract version information from git repository --- .gitignore | 2 + doc/tutorial/source/getting-started.rst | 103 ++++++++ src/core/examples/build-version-example.cc | 71 ++++++ src/core/examples/command-line-example.cc | 10 +- src/core/examples/wscript | 4 + src/core/model/command-line.cc | 33 ++- src/core/model/command-line.h | 59 +++-- src/core/model/version-defines.h.in | 145 +++++++++++ src/core/model/version.cc | 191 ++++++++++++++ src/core/model/version.h | 275 +++++++++++++++++++++ src/core/wscript | 73 +++++- waf-tools/versioning.py | 254 +++++++++++++++++++ 12 files changed, 1190 insertions(+), 30 deletions(-) create mode 100644 src/core/examples/build-version-example.cc create mode 100644 src/core/model/version-defines.h.in create mode 100644 src/core/model/version.cc create mode 100644 src/core/model/version.h create mode 100644 waf-tools/versioning.py diff --git a/.gitignore b/.gitignore index 4e52eb0fd..c625606e4 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ build-dir/ build/ /.cproject /.project + +version.cache diff --git a/doc/tutorial/source/getting-started.rst b/doc/tutorial/source/getting-started.rst index 0a8552c3e..e9530a284 100644 --- a/doc/tutorial/source/getting-started.rst +++ b/doc/tutorial/source/getting-started.rst @@ -1155,3 +1155,106 @@ except that the program and ns-3 libraries will not be rebuilt. .. sourcecode:: bash $ ./waf --run-no-build ' --arg1=value1 --arg2=value2 ...' + +Build version ++++++++++++++ + +As of the ns-3.32 release, a new Waf option was introduced to print out the +version of the ns-3 build + +.. sourcecode:: bash + + $ ./waf --check-version + +Waf will collect information about the build and print out something similar +to the output below. + +.. sourcecode:: text + + ns-3.31+26@g82e7f5d-debug + +The output of ``--check-version`` depends on how ns-3 was installed. If ns-3 +was installed from a tarball, the build information is included in one +of the files in the tarball. In this case, the output of ``--check-version`` +will always be the same, except for the profile value which is based +on the ``--build-profile`` option passed to ``waf configure``. + +If ns-3 was downloaded using git, the build information is generated by +examining the current state of the git repository. The output of +``--check-version`` will change whenever the state of the active branch +changes. + +The output of ``--check-version`` has the following format: + +.. sourcecode:: text + + [+closest_tag][+distance_from_tag]@[-tree_state]- + +version_tag + version_tag contains the version of the ns-3 code. The version tag is + defined as a git tag with the format ns-3*. If multiple git tags match the + format, the tag on the active branch which is closest to the current commit + is chosen. + +closest_tag + closest_tag is similar to version_tag except it is the first tag found, + regardless of format. The closest tag is not included in the output when + closest_tag and version_tag have the same value. + +distance_from_tag + distance_from_tag contains the number of commits between the current commit + and closest_tag. distance_from_tag is not included in the output when the + value is 0 (i.e. when closest_tag points to the current commit) + +commit_hash + commit_hash is the hash of the commit at the tip of the active branch. The + value is 'g' followed by the first 7 characters of the commit hash. The 'g' + prefix is used to indicate that this is a git hash. + +tree_state + tree_state indicates the state of the working tree. When the working tree + has uncommitted changes this field has the value 'dirty'. The tree state is + not included in the version output when the working tree is clean (e.g. when + there are no uncommitted changes). + +profile + The build profile specified in the ``--build-profile`` option passed to + ``waf configure`` + +A new class, named Version, has been added to the core module. The Version class +contains functions to retrieve individual fields of the build version as well +as functions to print the full build version like ``--check-version``. +The ``build-version-example`` application provides an example of how to use +the Version class to retrieve the various build version fields. See the +documentation for the Version class for specifics on the output of the Version +class functions. + +.. sourcecode:: text + + build-version-example: + Program Version (according to CommandLine): ns-3.31+28@gce1eb40-dirty-debug + + Version fields: + LongVersion: ns-3.32+28@gcefeb91-dirty-debug + ShortVersion: ns-3.32+* + BuildSummary: ns-3.32+* + VersionTag: ns-3.32 + Major: 3 + Minor: 32 + Patch: 0 + ReleaseCandidate: + ClosestAncestorTag: ns-3.32 + TagDistance: 28 + CommitHash: gce1eb40 + BuildProfile: debug + WorkingTree: dirty + +The CommandLine class has also been updated to support the ``--version`` +option which will print the full build version and exit. + +.. sourcecode:: text + + ./waf --run-no-build "command-line-example --version" + Waf: Entering directory `/g/g14/mdb/gitlab/mdb/ns-3-dev/build/debug' + ns-3.31+28@gce1eb40-dirty-debug + diff --git a/src/core/examples/build-version-example.cc b/src/core/examples/build-version-example.cc new file mode 100644 index 000000000..0e6e08b2d --- /dev/null +++ b/src/core/examples/build-version-example.cc @@ -0,0 +1,71 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2018 Lawrence Livermore National Laboratory + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Peter D. Barnes, Jr. + */ + +#include "ns3/command-line.h" +#include "ns3/version.h" + +#include +#include +#include + +/** + * \file + * \ingroup core-examples + * Example program illustrating use of ns3::Version. + */ + +using namespace ns3; + +int main (int argc, char *argv[]) +{ + + CommandLine cmd (__FILE__); + cmd.Usage ("Version class example program.\n" + "\n" + "This program demonstrates the various outputs from the Version class"); + cmd.Parse (argc, argv); + + std::cout << std::endl; + std::cout << cmd.GetName () << ":" << std::endl; + + //Print the source version used to build this example + std::cout << "Program Version (according to CommandLine): "; + cmd.PrintVersion (std::cout); + std::cout << std::endl; + + Version version; + std::cout << "Version fields:\n" + << "LongVersion: " << version.LongVersion () << "\n" + << "ShortVersion: " << version.ShortVersion () << "\n" + << "BuildSummary: " << version.BuildSummary () << "\n" + << "VersionTag: " << version.VersionTag () << "\n" + << "Major: " << version.Major () << "\n" + << "Minor: " << version.Minor () << "\n" + << "Patch: " << version.Patch () << "\n" + << "ReleaseCandidate: " << version.ReleaseCandidate () << "\n" + << "ClosestAncestorTag: " << version.ClosestAncestorTag () << "\n" + << "TagDistance: " << version.TagDistance () << "\n" + << "CommitHash: " << version.CommitHash () << "\n" + << "BuildProfile: " << version.BuildProfile () << "\n" + << "WorkingTree: " << (version.DirtyWorkingTree () ? "dirty" : "clean") + << std::endl; + + return 0; +} diff --git a/src/core/examples/command-line-example.cc b/src/core/examples/command-line-example.cc index 4085baf38..e4a1b6d54 100644 --- a/src/core/examples/command-line-example.cc +++ b/src/core/examples/command-line-example.cc @@ -28,7 +28,7 @@ * \file * \ingroup core-examples * \ingroup commandline - * \brief Example program illustrating use of ns3::CommandLine. + * Example program illustrating use of ns3::CommandLine. */ using namespace ns3; @@ -104,7 +104,13 @@ int main (int argc, char *argv[]) // Show initial values: std::cout << std::endl; - std::cout << cmd.GetName () << std::endl; + std::cout << cmd.GetName () << ":" << std::endl; + + //Print the source version used to build this example + std::cout << "Program Version: "; + cmd.PrintVersion (std::cout); + std::cout << std::endl; + std::cout << "Initial values:" << std::endl; std::cout << std::left << std::setw (10) << "intArg:" diff --git a/src/core/examples/wscript b/src/core/examples/wscript index d96c43a9b..96c9f5c70 100644 --- a/src/core/examples/wscript +++ b/src/core/examples/wscript @@ -51,6 +51,10 @@ def build(bld): ['core']) obj.source = 'system-path-examples.cc' + obj = bld.create_ns3_program('build-version-example', + ['core']) + obj.source = 'build-version-example.cc' + if bld.env['ENABLE_THREADING'] and bld.env["ENABLE_REAL_TIME"]: obj = bld.create_ns3_program('main-test-sync', ['network']) obj.source = 'main-test-sync.cc' diff --git a/src/core/model/command-line.cc b/src/core/model/command-line.cc index 140d33492..f6a7fcdbb 100644 --- a/src/core/model/command-line.cc +++ b/src/core/model/command-line.cc @@ -27,6 +27,7 @@ #include "system-path.h" #include "type-id.h" #include "string.h" +#include "version.h" #include // transform #include // tolower @@ -64,7 +65,7 @@ CommandLine::CommandLine (const std::string filename) std::string basename = SystemPath::Split (filename).back (); m_shortName = basename.substr (0, basename.rfind (".cc")); } - + CommandLine::CommandLine (const CommandLine &cmd) { Copy (cmd); @@ -137,7 +138,7 @@ CommandLine::Parse (std::vector args) NS_LOG_FUNCTION (this << args.size () << args); PrintDoxygenUsage (); - + m_nonOptionCount = 0; if (args.size () > 0) @@ -328,11 +329,23 @@ CommandLine::PrintHelp (std::ostream &os) const << " --PrintGroup=[group]: Print all TypeIds of group.\n" << " --PrintTypeIds: Print all TypeIds.\n" << " --PrintAttributes=[typeid]: Print all attributes of typeid.\n" + << " --PrintVersion: Print the ns-3 version.\n" << " --PrintHelp: Print this help message.\n" << std::endl; } #include // getcwd +std::string +CommandLine::GetVersion () const +{ + return Version::LongVersion (); +} + +void +CommandLine::PrintVersion (std::ostream & os) const +{ + os << GetVersion () << std::endl; +} void CommandLine::PrintDoxygenUsage (void) const @@ -344,7 +357,7 @@ CommandLine::PrintDoxygenUsage (void) const { return; } - + if (m_shortName.size () == 0) { NS_FATAL_ERROR ("No file name on example-to-run; forgot to use CommandLine var (__FILE__)?"); @@ -356,19 +369,19 @@ CommandLine::PrintDoxygenUsage (void) const m_nonOptions.begin () + m_NNonOptions); std::string outf = SystemPath::Append (std::string (envVar), m_shortName + ".command-line"); - + NS_LOG_INFO ("Writing CommandLine doxy to " << outf); - + std::fstream os (outf, std::fstream::out); - + os << "/**\n \\file " << m_shortName << ".cc\n" << "

Usage

\n" << "$ ./waf --run \"" << m_shortName << (m_options.size () ? " [Program Options]" : "") << (nonOptions.size () ? " [Program Arguments]" : "") << "\"\n"; - + if (m_usage.length ()) { os << m_usage << std::endl; @@ -577,6 +590,12 @@ CommandLine::HandleArgument (const std::string &name, const std::string &value) PrintHelp (std::cout); std::exit (0); } + if (name == "PrintVersion" || name == "version") + { + //Print the version, then exit the program + PrintVersion (std::cout); + std::exit (0); + } else if (name == "PrintGroups") { // method below never returns. diff --git a/src/core/model/command-line.h b/src/core/model/command-line.h index 2095f7e44..42df8fda1 100644 --- a/src/core/model/command-line.h +++ b/src/core/model/command-line.h @@ -64,9 +64,12 @@ namespace ns3 { --PrintGroup=[group]: Print all TypeIds of group. --PrintTypeIds: Print all TypeIds. --PrintAttributes=[typeid]: Print all attributes of typeid. + --PrintVersion: Print the ns-3 version. --PrintHelp: Print this help message. \endverbatim * - * The more common \c --help is a synonym for \c --PrintHelp; an example + * The more common \c \--version is a synonym for \c \--PrintVersion. + * + * The more common \c \--help is a synonym for \c \--PrintHelp; an example * is given below. * * CommandLine can also handle non-option arguments @@ -82,7 +85,7 @@ namespace ns3 { * In use, arguments are given in the form * \verbatim --arg=value --toggle first-non-option\endverbatim - * Most arguments expect a value, as in the first form, \c --arg=value. + * Most arguments expect a value, as in the first form, \c \--arg=value. * Toggles, corresponding to boolean arguments, can be given in any of * the forms * \verbatim @@ -98,28 +101,29 @@ namespace ns3 { * will be the final value used. For example, * \verbatim --arg=one --toggle=f --arg=another --toggle \endverbatim - * The variable set by \c --arg will end up with the value \c "another"; - * the boolean set by \c --toggle will end up as \c true. + * The variable set by \c \--arg will end up with the value \c "another"; + * the boolean set by \c \--toggle will end up as \c true. * * Because arguments can be repeated it can be hard to decipher what * value each variable ended up with, especially when using boolean toggles. * Suggested best practice is for scripts to report the values of all items * settable through CommandLine, as done by the example below. * + * * CommandLine can set the initial value of every attribute in the system - * with the \c --TypeIdName::AttributeName=value syntax, for example + * with the \c \--TypeIdName::AttributeName=value syntax, for example * \verbatim --Application::StartTime=3s \endverbatim * In some cases you may want to highlight the use of a particular * attribute for a simulation script. For example, you might want * to make it easy to set the \c Application::StartTime using - * the argument \c --start, and have its help string show as part + * the argument \c \--start, and have its help string show as part * of the help message. This can be done using the * \link AddValue(const std::string&, const std::string&) AddValue (name, attributePath) \endlink * method. * * CommandLine can also set the value of every GlobalValue - * in the system with the \c --GlobalValueName=value syntax, for example + * in the system with the \c \--GlobalValueName=value syntax, for example * \verbatim --SchedulerType=HeapScheduler \endverbatim * @@ -182,6 +186,7 @@ namespace ns3 { --PrintGroup=[group]: Print all TypeIds of group. --PrintTypeIds: Print all TypeIds. --PrintAttributes=[typeid]: Print all attributes of typeid. + --PrintVersion: Print the ns-3 version. --PrintHelp: Print this help message. \endverbatim * * Having parsed the arguments, some programs will need to perform @@ -225,7 +230,7 @@ public: CommandLine (void); /** * Construct and register the source file name. - * This would typically be called by + * This would typically be called by * CommandLine cmd (__FILE__); * * This form is required to generate Doxygen documentation of the @@ -253,7 +258,7 @@ public: /** * Supply the program usage and documentation. * - * \param [in] usage Program usage message to write with \c --help. + * \param [in] usage Program usage message to write with \c \--help. */ void Usage (const std::string usage); @@ -261,7 +266,7 @@ public: * Add a program argument, assigning to POD * * \param [in] name The name of the program-supplied argument - * \param [in] help The help text used by \c \-\-PrintHelp + * \param [in] help The help text used by \c \--PrintHelp * \param [out] value A reference to the variable where the * value parsed will be stored (if no value * is parsed, this variable is not modified). @@ -283,7 +288,7 @@ public: * Add a program argument, using a Callback to parse the value * * \param [in] name The name of the program-supplied argument - * \param [in] help The help text used by \c --help + * \param [in] help The help text used by \c \--help * \param [in] callback A Callback function that will be invoked to parse and * store the value. * \param [in] defaultValue Optional default value for argument. @@ -310,7 +315,7 @@ public: * Add a non-option argument, assigning to POD * * \param [in] name The name of the program-supplied argument - * \param [in] help The help text used by \c \-\-PrintHelp + * \param [in] help The help text used by \c \--PrintHelp * \param [out] value A reference to the variable where the * value parsed will be stored (if no value * is parsed, this variable is not modified). @@ -378,7 +383,7 @@ public: /** * \brief Print program usage to the desired output stream * - * Handler for \c \-\-PrintHelp and \c \-\-help: print Usage(), argument names, and help strings + * Handler for \c \--PrintHelp and \c \--help: print Usage(), argument names, and help strings * * Alternatively, an overloaded operator << can be used: * \code @@ -393,6 +398,22 @@ public: */ void PrintHelp (std::ostream &os) const; + /** + * Get the program version. + * + * \return The program version + */ + std::string GetVersion () const; + + /** + * Print ns-3 version to the desired output stream + * + * Handler for \c \--PrintVersion and \c \--version. + * + * \param [in,out] os The output stream to print on. + */ + void PrintVersion (std::ostream &os) const; + private: /** @@ -402,7 +423,7 @@ private: class Item { public: - std::string m_name; /**< Argument label: \c \-\--m_name=... */ + std::string m_name; /**< Argument label: \c \--m_name=... */ std::string m_help; /**< Argument help string */ virtual ~Item (); /**< Destructor */ /** @@ -511,32 +532,32 @@ private: static bool HandleAttribute (const std::string name, const std::string value); /** - * Handler for \c \-\-PrintGlobals: print all global variables and values + * Handler for \c \--PrintGlobals: print all global variables and values * \param [in,out] os The output stream to print on. */ void PrintGlobals (std::ostream &os) const; /** - * Handler for \c \-\-PrintAttributes: print the attributes for a given type. + * Handler for \c \--PrintAttributes: print the attributes for a given type. * * \param [in,out] os the output stream. * \param [in] type The TypeId whose Attributes should be displayed */ void PrintAttributes (std::ostream &os, const std::string &type) const; /** - * Handler for \c \-\-PrintGroup: print all types belonging to a given group. + * Handler for \c \--PrintGroup: print all types belonging to a given group. * * \param [in,out] os The output stream. * \param [in] group The name of the TypeId group to display */ void PrintGroup (std::ostream &os, const std::string &group) const; /** - * Handler for \c \-\-PrintTypeIds: print all TypeId names. + * Handler for \c \--PrintTypeIds: print all TypeId names. * * \param [in,out] os The output stream. */ void PrintTypeIds (std::ostream &os) const; /** - * Handler for \c \-\-PrintGroups: print all TypeId group names + * Handler for \c \--PrintGroups: print all TypeId group names * * \param [in,out] os The output stream. */ diff --git a/src/core/model/version-defines.h.in b/src/core/model/version-defines.h.in new file mode 100644 index 000000000..ad70eeb72 --- /dev/null +++ b/src/core/model/version-defines.h.in @@ -0,0 +1,145 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2018 Lawrence Livermore National Laboratory + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: Mathew Bielejeski + */ + +#ifndef NS3_VERSION_DEFINES_H_ +#define NS3_VERSION_DEFINES_H_ + +/** + * \file + * \ingroup buildversion + * Defines the macro values for printing the build version. + * These will be populated by the build system. + */ + +/** + * \ingroup buildversion + * @{ + */ + +/** + * The first tag found which matches the pattern ns-3*. + * + * The expected format of the ns-3 version tag is: ns-3.[.patch][-release_candidate] + * + * The tag is found by starting at the tip of the current branch and walking + * back towards the root. + * + * Type: string literal + */ +#define NS3_VERSION_TAG @VERSION_TAG@ + +/** + * The tag closest to the tip of the current branch + * + * The tag is found by starting at the tip of the current branch and + * walking back towards the root. + * + * This value will be the same as #NS3_VERSION_TAG when #NS3_VERSION_TAG is + * the closest tag. + * + * Type: string literal + */ +#define NS3_VERSION_CLOSEST_TAG @CLOSEST_TAG@ + +/** + * The major version extracted from #NS3_VERSION_TAG + * + * For a tag with the format ns-.[.digit], the major + * version is the number after ns- and before the first period. + * + * Type: integer + */ +#define NS3_VERSION_MAJOR @VERSION_MAJOR@ + +/** + * The minor version extracted from #NS3_VERSION_TAG + * + * For a tag with the format ns-.[.digit], the minor + * version is the number after the first period. + * + * Type: integer + */ +#define NS3_VERSION_MINOR @VERSION_MINOR@ + +/** + * The patch number extracted from #NS3_VERSION_TAG + * + * For a tag with the format ns-.[.digit], the patch + * is the number after the last period. + * + * The patch value is optional and may not be present in the tag. + * In cases where the patch value is not present, the field will be set to 0 + * + * Type: integer + */ +#define NS3_VERSION_PATCH @VERSION_PATCH@ + +/** + * The portion of the #NS3_VERSION_TAG indicating the version + * of the release candidate (if applicable). + * + * In order for this field to contain a value, the #NS3_VERSION_TAG + * must have the format ns-.[.digit][-RC]. + * The contents of the release candidate will be the RC + * portion of the tag. + * + * Type: string literal + */ +#define NS3_VERSION_RELEASE_CANDIDATE @VERSION_RELEASE_CANDIDATE@ + +/** + * The number of repository commits between #NS3_VERSION_CLOSEST_TAG + * and the branch HEAD. + * + * Type: integer + */ +#define NS3_VERSION_TAG_DISTANCE @VERSION_TAG_DISTANCE@ + +/** + * Hash value which uniquely identifies the commit of the + * branch HEAD. + * The first character of the commit hash is 'g' to indicate this hash is + * a git hash + * + * Type: string literal + */ +#define NS3_VERSION_COMMIT_HASH @VERSION_COMMIT_HASH@ + +/** + * Flag indicating whether the repository working tree had uncommitted + * changes when the library was built. + * + * The flag will be 1 if there were uncommitted changes, 0 otherwise. + * + * Type: integer + */ +#define NS3_VERSION_DIRTY_FLAG @VERSION_DIRTY_FLAG@ + +/** + * Indicates the build profile that was specified by the --build-profile option + * of "waf configure" + * + * Type: string literal + */ +#define NS3_VERSION_BUILD_PROFILE "@BUILD_PROFILE@" + +/** @} */ + +#endif diff --git a/src/core/model/version.cc b/src/core/model/version.cc new file mode 100644 index 000000000..d454a4839 --- /dev/null +++ b/src/core/model/version.cc @@ -0,0 +1,191 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2018 Lawrence Livermore National Laboratory + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: Mathew Bielejeski + */ + +#include "version.h" +#include "version-defines.h" +#include + +/** + * \file + * \ingroup buildversion + * ns3::Version implementation + */ + +namespace ns3 { + +std::string +Version::VersionTag (void) +{ + return NS3_VERSION_TAG; +} + +std::string +Version::ClosestAncestorTag (void) +{ + return NS3_VERSION_CLOSEST_TAG; +} + +uint32_t +Version::Major (void) +{ + return NS3_VERSION_MAJOR; +} + +uint32_t +Version::Minor (void) +{ + return NS3_VERSION_MINOR; +} + +uint32_t +Version::Patch (void) +{ + return NS3_VERSION_PATCH; +} + +std::string +Version::ReleaseCandidate (void) +{ + return std::string{NS3_VERSION_RELEASE_CANDIDATE}; +} + +uint32_t +Version::TagDistance (void) +{ + return NS3_VERSION_TAG_DISTANCE; +} + +bool +Version::DirtyWorkingTree (void) +{ + return static_cast (NS3_VERSION_DIRTY_FLAG); +} + +std::string +Version::CommitHash (void) +{ + return std::string{NS3_VERSION_COMMIT_HASH}; +} + +std::string +Version::BuildProfile (void) +{ + return std::string{NS3_VERSION_BUILD_PROFILE}; +} + +std::string +Version::ShortVersion (void) +{ + std::ostringstream ostream; + ostream << "ns-" << Major () + << "." << Minor (); + + //only include patch if it is not 0 + auto patch = Patch (); + if (patch > 0) + { + ostream << '.' << patch; + } + + auto rc = ReleaseCandidate (); + if (!rc.empty ()) + { + ostream << '-' << rc; + } + + auto ancestorTag = ClosestAncestorTag (); + if ( ( !ancestorTag.empty () && (ancestorTag != VersionTag ()) ) + || TagDistance () > 0) + { + ostream << "+"; + } + if (DirtyWorkingTree ()) + { + ostream << "*"; + } + + return ostream.str (); +} + +std::string +Version::BuildSummary (void) +{ + std::ostringstream ostream; + ostream << ClosestAncestorTag (); + + if (TagDistance () > 0) + { + ostream << "+"; + } + if (DirtyWorkingTree ()) + { + ostream << "*"; + } + + return ostream.str (); +} + +std::string +Version::LongVersion (void) +{ + std::ostringstream ostream; + ostream << "ns-" << Major () + << "." << Minor (); + + //only include patch if it is not 0 + auto patch = Patch (); + if (patch > 0) + { + ostream << '.' << patch; + } + + auto rc = ReleaseCandidate (); + if (!rc.empty ()) + { + ostream << '-' << rc; + } + + auto ancestorTag = ClosestAncestorTag (); + if ( !ancestorTag.empty () && (ancestorTag != VersionTag ()) ) + { + ostream << '+' << ancestorTag; + } + + auto tagDistance = TagDistance (); + + if ( tagDistance > 0 ) + { + ostream << '+' << tagDistance; + } + + ostream << '@' << CommitHash (); + + if ( DirtyWorkingTree () ) + { + ostream << "-dirty"; + } + + ostream << '-' << BuildProfile (); + + return ostream.str (); +} + +} // namespace ns3 + diff --git a/src/core/model/version.h b/src/core/model/version.h new file mode 100644 index 000000000..117e92545 --- /dev/null +++ b/src/core/model/version.h @@ -0,0 +1,275 @@ +/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ +/* + * Copyright (c) 2018 Lawrence Livermore National Laboratory + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Authors: Mathew Bielejeski + */ + +#ifndef BUILD_VERSION_H_ +#define BUILD_VERSION_H_ + +#include "ns3/int64x64.h" +#include + +/** + * \file + * \ingroup buildversion + * class ns3::Version definition + */ + +namespace ns3 { + +/** + * \ingroup core + * \defgroup buildversion Build version reporting + * + * Version information is pulled from the local git repository during the build + * process. If a git repository is not found, the build system will search for + * a file named version.cache under the src/core directory. The version.cache + * file must contain key/value pairs in the format key=value with one entry per + * line. The build system will use the data pulled from the git repository, or + * loaded from the version.cache file, to generate the file version-defines.h + * + * The build will fail if a local git repository is not present and + * a version.cache file can not be found. + */ + +/** + * \ingroup buildversion + * + * Helper class providing functions to access various parts of the version + * string, as well as functions for composing short and long variants of the + * version string. + * + * See CommandLine::PrintVersion() for an example on how to + * use Version to output a version string. command-line-example has been updated + * to include CommandLine::PrintVersion() in its output + * + * build-version-example.cc illustrates using each of these functions. + * + * Below is a partial view of a git branch: + * + * \note Square nodes represent tags + * \note Circle nodes represent commits + * + * \dot + * digraph { + * vt [label="ns-3.32", shape="box"] + * t [label="mytag", shape="box"] + * h [label="HEAD", shape="box"] + * c1 [label="6ad7f05"] + * c2 [label="05fc891"] + * c3 [label="bd9ffac"] + * c4 [label="70fa23b"] + * + * h -> c1 -> c2 -> c3 -> c4 + * t -> c2 + * vt -> c4 + * } + * \enddot + * + * Here are the values that will be assigned based on this example branch: + * + * | Component | Value | Notes | + * |--------------------|----------|-------| + * | VersionTag | ns-3.32 | | + * | ClosestAncestorTag | mytag | | + * | Major | 3 | | + * | Minor | 32 | | + * | Patch | 0 | This version tag does not have a patch field | + * | ReleaseCandidate | "" | This version tag does not have a release candidate field | + * | TagDistance | 1 | | + * | CommitHash | g6ad7f05 | g at front of hash indicates a git hash | + * | DirtyWorkingTree | Variable | Depends on status of git working and stage areas | + * | BuildProfile | Variable | Depends on the value of --build-profile option of waf configure | + */ +class Version +{ +public: + /** + * Returns the ns-3 version tag of the closest ancestor commit. + * + * The format of the tag is + * \verbatim ns3-.[.patch] \endverbatim + * + * The patch field is optional and may not be present. The value of + * patch defaults to 0 if the tag does not have a patch field. + * + * \return ns-3 version tag + */ + static std::string VersionTag (void); + + /** + * Returns the closest tag that is attached to a commit that is an ancestor + * of the current branch head. + * + * The value returned by this function may be the same as VersionTag() + * if the ns-3 version tag is the closest ancestor tag. + * + * \return Closest tag attached to an ancestor of the current commit + */ + static std::string ClosestAncestorTag (void); + + /** + * Major component of the build version + * + * The format of the build version string is + * \verbatim ns-.[.patch][-RC] \endverbatim + * + * The major component is the number before the first period + * + * \return The major component of the build version + */ + static uint32_t Major (void); + + /** + * Minor component of the build version + * + * The format of the build version string is + * \verbatim ns-.[.patch][-RC] \endverbatim + * + * The minor component is the number after the first period + * + * \return The minor component of the build version + */ + static uint32_t Minor (void); + + /** + * Patch component of the build version + * + * A build version with a patch component will have the format + * \verbatim ns-.. \endverbatim + * + * The patch component is the number after the second period + * + * \return The patch component of the build version or 0 if the build version + * does not have a patch component + */ + static uint32_t Patch (void); + + /** + * Release candidate component of the build version + * + * A build version with a release candidate will have the format + * \verbatim ns-.[.patch]-RC \endverbatim + * + * The string returned by this function will have the format RC + * + * \return The release candidate component of the build version or an empty + * string if the build version does not have a release candidate component + */ + static std::string ReleaseCandidate (void); + + /** + * The number of commits between the current + * commit and the tag returned by ClosestAncestorTag(). + * + * \return The number of commits made since the last tagged commit + */ + static uint32_t TagDistance (void); + + /** + * Indicates whether there were uncommitted changes during the build + * + * \return \c true if the working tree had uncommitted changes. + */ + static bool DirtyWorkingTree (void); + + /** + * Hash of the most recent commit + * + * The hash component is the id of the most recent commit. + * The returned value is a hexadecimal string with enough data to + * uniquely identify the commit. + * + * The first character of the string is a letter indicating the type + * of repository that was in use: g=git + * + * Example of hash output: g6bfb0c9 + * + * \return hexadecimal representation of the most recent commit id + */ + static std::string CommitHash (void); + + /** + * Indicates the type of build that was performed (debug/release/optimized). + * + * This information is set by the --build-profile option of waf configure + * + * \return String containing the type of build + */ + static std::string BuildProfile (void); + + /** + * Constructs a string containing the ns-3 major and minor version components, + * and indication of additional commits or dirty status. + * + * The format of the constructed string is + * \verbatim ns-.[.patch][-rc] \endverbatim + * + * * [patch] is included when Patch() > 0. + * * [-rc] is included when ReleaseCandidate() contains a non-empty string + * * \c flags will contain `+` when TagDistance() > 0 + * * \c flags will contain `*` when DirtyWorkingTree() == true. + * + * [flags] will contain none, one, or both characters depending on the state + * of the branch + * + * \return String containing the ns-3 major and minor components and flags. + */ + static std::string ShortVersion (void); + + /** + * Constructs a string containing the most recent tag and status flags. + * + * In the case where closest-ancestor-tag == version-tag, the output of this + * function will be the same as ShortVersion() + * + * The format of the constructed string is `[flags]`. + * + * * \c flags will contain `+` when TagDistance() > 0 + * * \c flags will contain `*` when DirtyWorkingTree() == true. + * + * [flags] will contain none, one, or both characters depending on the state + * of the branch + * + * \return String containing the closest ancestor tag and flags. + */ + static std::string BuildSummary (void); + + /** + * Constructs a string containing all of the build details + * + * The format of the constructed string is + * \verbatim + * ns-.[.patch][-rc][-closest-tag]-@[-dirty]- + * \endverbatim + * + * [patch], [rc], [closest-tag], and [dirty] will only be present under certain circumstances: + * * [patch] is included when Patch() > 0 + * * [rc] is included when ReleaseCandidate() is not an empty string + * * [closest-tag] is included when ClosestTag() != VersionTag() + * * [dirty] is included when DirtyWorkingTree() is \c true + * + * \return String containing full version + */ + static std::string LongVersion (void); + +}; // class Version + +} // namespace ns3 + +#endif diff --git a/src/core/wscript b/src/core/wscript index 3cb6b5e48..47737c892 100644 --- a/src/core/wscript +++ b/src/core/wscript @@ -1,7 +1,8 @@ ## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- import sys +import re -from waflib import Options, Utils +from waflib import Context, Configure, Options, Utils import wutils int64x64 = { @@ -41,9 +42,15 @@ def options(opt): action="store_true", default=False, dest='disable_pthread') - + opt.add_option('--check-version', + help=("Print the current build version"), + action="store_true", default=False, + dest='check_version') def configure(conf): + + conf.load('versioning', ['waf-tools']) + int64x64_impl = Options.options.int64x64_impl if int64x64_impl == 'default' or int64x64_impl == 'int128': @@ -150,8 +157,26 @@ int main () conf.write_config_header('ns3/core-config.h', top=True) def build(bld): + + bld.install_files('${INCLUDEDIR}/%s%s/ns3' % (wutils.APPNAME, wutils.VERSION), '../../ns3/core-config.h') + version_template = bld.path.find_node('model/version-defines.h.in') + version_header = bld.bldnode.make_node('version-defines.h') + + vers_tg = bld(features='version-defines', + source=version_template, + target=version_header) + + #silence errors about no mapping for version-defines.h.in + vers_tg.mappings['.h'] = lambda *a, **k: None + vers_tg.mappings['.h.in'] = lambda *a, **k: None + + if Options.options.check_version: + print_version(bld, vers_tg) + raise SystemExit(0) + return + core = bld.create_ns3_module('core') core.source = [ 'model/time.cc', @@ -214,6 +239,7 @@ def build(bld): 'model/time-printer.cc', 'model/show-progress.cc', 'model/system-wall-clock-timestamp.cc', + 'model/version.cc', ] if (bld.env['ENABLE_EXAMPLES']): @@ -342,6 +368,7 @@ def build(bld): 'model/node-printer.h', 'model/time-printer.h', 'model/show-progress.h', + 'model/version.h', ] if (bld.env['ENABLE_EXAMPLES']): @@ -415,3 +442,45 @@ def build(bld): pymod = bld.ns3_python_bindings() if pymod is not None: pymod.source += ['bindings/module_helpers.cc'] + +def print_version(bld, tg): + tg.post() + + found = False + for task in tg.tasks: + if task.__class__.__name__ == 'git_ns3_version_info': + found = True + #manually run task + task.run() + break + + if not found: + print("Task to generate version information could not be found") + return + + handlers = { + 'VERSION_TAG': lambda s: s.strip('"'), + 'CLOSEST_TAG': lambda s: s.strip('"'), + 'VERSION_TAG_DISTANCE': lambda s: '' if s == '0' else "+" + s, + 'VERSION_COMMIT_HASH': lambda s: "@" + s.strip('"'), + 'VERSION_DIRTY_FLAG': lambda s: '' if s == '0' else '-dirty', + 'BUILD_PROFILE': lambda s: "-" + s + } + + fields=('VERSION_TAG', 'CLOSEST_TAG', 'VERSION_TAG_DISTANCE', + 'VERSION_COMMIT_HASH', 'VERSION_DIRTY_FLAG', 'BUILD_PROFILE') + + parts = dict() + + for field in fields: + if field in bld.env: + parts[field] = handlers[field](bld.env[field]) + else: + parts[field] = '' + + if parts['CLOSEST_TAG'] != parts['VERSION_TAG']: + parts['CLOSEST_TAG'] = "+" + parts['CLOSEST_TAG'] + else: + parts['CLOSEST_TAG'] = "" + + print(''.join([parts[f] for f in fields])) diff --git a/waf-tools/versioning.py b/waf-tools/versioning.py new file mode 100644 index 000000000..b19beb6a5 --- /dev/null +++ b/waf-tools/versioning.py @@ -0,0 +1,254 @@ +## -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*- + +import re + +from waflib import ConfigSet, Configure, Context, Task, TaskGen + +CACHE_FILE = 'version.cache' + +class ns3_version_info(Task.Task): + '''Base task which implements functionality common to all inherited tasks + + This class handles parsing the ns-3 version tag into component parts + as well as saving the version fields to a cache file + + All version fields should be stored in the fields property + + Derived classes should override _find_closest_tag() and + _find_closest_ns3_tag() + ''' + + def __init__(self, *args, **kwargs): + self._fields = ConfigSet.ConfigSet() + + super(ns3_version_info, self).__init__(*args, **kwargs) + + @property + def fields(self): + return self._fields + + def _find_closest_tag(self, ctx): + """Override in derived classes""" + pass + + def _find_closest_ns3_tag(self, ctx): + """Override in derived classes""" + pass + + def _parse_version_tag(self, ctx, tag): + safe_tag = tag.strip() + matches = re.match("ns-(\d+)\.(\d+)(?:.(\d+))?(?:-(RC.+))?.*", safe_tag) + + if not matches: + return False + + self.fields['VERSION_TAG'] = '"{}"'.format(safe_tag) + self.fields['VERSION_MAJOR'] = matches.group(1) + self.fields['VERSION_MINOR'] = matches.group(2) + + patch = matches.group(3) + + if not patch: + patch = '0' + + self.fields['VERSION_PATCH'] = patch + + release_candidate = matches.group(4) + + if not release_candidate: + release_candidate = '' + + self.fields['VERSION_RELEASE_CANDIDATE'] = '"{}"'.format(release_candidate) + + return True + + def run(self): + ctx = self.generator.bld + + try: + self._find_closest_tag(ctx) + + if 'VERSION_TAG' not in self.fields: + self._find_closest_ns3_tag(ctx) + + #Generate the path where the cache file will be stored + base_path = self.generator.path.make_node('model') + cache_path = base_path.make_node(CACHE_FILE) + + #Write the version information out to the cache file + #The cache file is used to populate the version fields when a git + #repository can not be found + self.fields.store(cache_path.abspath()) + + #merge version info into main configset + ctx.env.update(self.fields) + + except Exception as e: + ctx.to_log("Extracting version information from tags failed: {}\n".format(e)) + return 1 + + return 0 + +class git_ns3_version_info(ns3_version_info): + '''Task to generate version fields from an ns-3 git repository''' + always_run = True + + def _find_closest_tag(self, ctx): + cmd = [ + 'git', + 'describe', + '--tags', + '--dirty', + '--long' + ] + + try: + out = ctx.cmd_and_log(cmd, + output=Context.STDOUT, + quiet=Context.BOTH) + except Exception as e: + raise Exception(e.stderr.strip()) + + matches = re.match('(.+)-(\d+)-(g[a-fA-F0-9]+)(?:-(dirty))?', out) + + if not matches: + raise ValueError("Closest tag found in git log" + "does not match the expected format (tag='{}')" + .format(out)) + + tag = matches.group(1) + + self.fields['CLOSEST_TAG'] = '"{}"'.format(tag) + self.fields['VERSION_TAG_DISTANCE'] = matches.group(2) + self.fields['VERSION_COMMIT_HASH'] = '"{}"'.format(matches.group(3)) + self.fields['VERSION_DIRTY_FLAG'] = '1' if matches.group(4) else '0' + + self._parse_version_tag(ctx, tag) + + def _find_closest_ns3_tag(self, ctx): + cmd = [ + 'git', + 'describe', + '--tags', + '--abbrev=0', + '--match', + 'ns-3*', + 'HEAD' + ] + + try: + out = ctx.cmd_and_log(cmd, + output=Context.STDOUT, + quiet=Context.BOTH) + except Exception as e: + raise Exception(e.stderr.strip()) + + tag = out.strip() + + result = self._parse_version_tag(ctx, tag) + + if not result: + raise ValueError("Closest ns3 tag found in git log" + "does not match the expected format (tag='{}')" + .format(tag)) + +@TaskGen.feature('version-defines') +def generate_version_defines(self): + + #Create a substitution task to generate version-defines.h + #from fields stored in env + subst_task = self.create_task('subst', self.source, self.target) + + if self.env['HAVE_GIT_REPO']: + #if a git repo is present, run the version task first to + #populate the appropriate fields with data from the git repo + version_task = self.create_task('git_ns3_version_info') + subst_task.set_run_after(version_task) + +@Configure.conf +def check_git_repo(self): + '''Determine if a git repository is present''' + + root = False + cmd = [ + 'git', + 'rev-parse', + '--show-toplevel' + ] + + try: + #determine if the current directory is part of a git repository + self.find_program('git') + + out = self.cmd_and_log(cmd, output=Context.STDOUT, quiet=Context.BOTH) + + root = out.strip() + except Exception: + root = False + + self.msg('Checking for local git repository', root) + + return bool(root) + +@Configure.conf +def check_git_repo_has_ns3_tags(self): + '''Determine if the git repository is an ns-3 repository + + A repository is considered an ns-3 repository if it has at least one + tag that matches the regex ns-3* + ''' + + tag = False + + cmd = [ + 'git', + 'describe', + '--tags', + '--abbrev=0', + '--match', + 'ns-3*' + ] + + try: + out = self.cmd_and_log(cmd, output=Context.STDOUT, quiet=Context.BOTH) + + tag = out.strip() + + except Exception: + tag = False + + self.msg('Checking local git repository for ns3 tags', tag) + + return bool(tag) + +def configure(ctx): + + has_ns3_tags = False + + if ctx.check_git_repo(): + has_ns3_tags = ctx.check_git_repo_has_ns3_tags() + + ctx.env['HAVE_GIT_REPO'] = has_ns3_tags + + if not has_ns3_tags: + #no ns-3 repository, look for a cache file containing the version info + ctx.start_msg('Searching for file {}'.format(CACHE_FILE)) + + glob_pattern = '**/{}'.format(CACHE_FILE) + cache_path = ctx.path.ant_glob(glob_pattern) + + if len(cache_path) == 0: + ctx.end_msg(False) + ctx.fatal("Could not find {} under {}. This file must exist and contain " + "version information when a git repository is not " + "present.".format(CACHE_FILE, ctx.path)) + + #Found cache file + #Load it and merge the information into the main context environment + src_path = cache_path[0].srcpath() + ctx.end_msg(src_path) + + version_cache = ConfigSet.ConfigSet () + version_cache.load (src_path) + ctx.env.update(version_cache) +