[rtems_waf commit] Add version and git support for apps to use

Chris Johns chrisj at rtems.org
Fri Apr 14 03:25:09 UTC 2023


Module:    rtems_waf
Branch:    master
Commit:    bbb77343a96302afbd78d589fa322cd56b98f1e8
Changeset: http://git.rtems.org/rtems_waf/commit/?id=bbb77343a96302afbd78d589fa322cd56b98f1e8

Author:    Chris Johns <chrisj at rtems.org>
Date:      Fri Apr 14 13:40:43 2023 +1000

Add version and git support for apps to use

---

 git.py     | 226 +++++++++++++++++++++++++++++++++++++++++++++++++++
 version.py | 270 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 496 insertions(+)

diff --git a/git.py b/git.py
new file mode 100644
index 0000000..1c90f0d
--- /dev/null
+++ b/git.py
@@ -0,0 +1,226 @@
+#
+# RTEMS Tools Project (http://www.rtems.org/)
+# Copyright 2010-2016, 2023 Chris Johns (chrisj at rtems.org)
+# All rights reserved.
+#
+# This file is part of the RTEMS Tools package in 'rtems-tools'.
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+#
+# Provide some basic access to the git command.
+#
+
+from __future__ import print_function
+
+import os
+import os.path
+
+class repo:
+    """An object to manage a git repo."""
+
+    def _git_exit_code(self, ec):
+        if ec:
+            raise self.ctx.fatal('git command failed (%s): %d' %
+                                 (self.git, ec))
+
+    def _run(self, args, check=False):
+        import waflib
+        if os.path.exists(self.path):
+            cwd = self.path
+        else:
+            cwd = None
+        cmd = [self.git] + args
+        exit_code = 0
+        try:
+            output = self.ctx.cmd_and_log(cmd,
+                                          cwd=cwd,
+                                          output=waflib.Context.STDOUT,
+                                          quiet=waflib.Context.BOTH)
+        except waflib.Errors.WafError as e:
+            exit_code = e.returncode
+            output = e.stderr
+        if check:
+            self._git_exit_code(exit_code)
+        return exit_code, output
+
+    def __init__(self, ctx, path):
+        self.ctx = ctx
+        self.path = path
+        self.git = 'git'
+
+    def git_version(self):
+        ec, output = self._run(['--version'], True)
+        gvs = output.split()
+        if len(gvs) < 3:
+            raise self.ctx.fatal('invalid version string from git: %s' %
+                                 (output))
+        vs = gvs[2].split('.')
+        if len(vs) not in [3, 4]:
+            raise self.ctx.fatal('invalid version number from git: %s' %
+                                 (gvs[2]))
+        return tuple(map(int, vs))
+
+    def clone(self, url, path):
+        ec, output = self._run(['clone', url, path], check=True)
+
+    def fetch(self):
+        ec, output = self._run(['fetch'], check=True)
+
+    def merge(self):
+        ec, output = self._run(['merge'], check=True)
+
+    def pull(self):
+        ec, output = self._run(['pull'], check=True)
+
+    def reset(self, args):
+        if type(args) == str:
+            args = [args]
+        ec, output = self._run(['reset'] + args, check=True)
+
+    def branch(self):
+        ec, output = self._run(['branch'])
+        if ec == 0:
+            for b in output.split(os.linesep):
+                if b[0] == '*':
+                    return b[2:]
+        return None
+
+    def checkout(self, branch='master'):
+        ec, output = self._run(['checkout', branch], check=True)
+
+    def submodule(self, module):
+        ec, output = self._run(['submodule', 'update', '--init', module],
+                               check=True)
+
+    def submodule_foreach(self, args=[]):
+        if type(args) == str:
+            args = [args.split(args)]
+        ec, output = self._run(
+            ['submodule', 'foreach', '--recursive', self.git] + args,
+            check=True)
+
+    def submodules(self):
+        smodules = {}
+        ec, output = self._run(['submodule'], check=True)
+        if ec == 0:
+            for l in output.split(os.linesep):
+                ms = l.split()
+                if len(ms) == 3:
+                    smodules[ms[1]] = (ms[0], ms[2][1:-1])
+        return smodules
+
+    def clean(self, args=[]):
+        if type(args) == str:
+            args = [args]
+        ec, output = self._run(['clean'] + args, check=True)
+
+    def status(self, submodules_always_clean=False):
+        _status = {}
+        if os.path.exists(self.path):
+            if submodules_always_clean:
+                submodules = self.submodules()
+            else:
+                submodules = {}
+            ec, output = self._run(['status'])
+            if ec == 0:
+                state = 'none'
+                for l in output.split(os.linesep):
+                    if l.startswith('# '):
+                        l = l[2:]
+                    if l.startswith('On branch '):
+                        _status['branch'] = l[len('On branch '):]
+                    elif l.startswith('Changes to be committed:'):
+                        state = 'staged'
+                    elif l.startswith('Changes not staged for commit:'):
+                        state = 'unstaged'
+                    elif l.startswith('Untracked files:'):
+                        state = 'untracked'
+                    elif l.startswith('HEAD detached'):
+                        state = 'detached'
+                    elif state != 'none' and len(l.strip()) != 0:
+                        if l[0].isspace():
+                            l = l.strip()
+                            if l[0] != '(':
+                                if ':' in l:
+                                    l = l.split(':')[1]
+                                if len(l.strip()) > 0:
+                                    l = l.strip()
+                                    ls = l.split()
+                                    if state != 'unstaged' or ls[
+                                            0] not in submodules:
+                                        if state not in _status:
+                                            _status[state] = [l]
+                                        else:
+                                            _status[state] += [l]
+        return _status
+
+    def dirty(self):
+        _status = self.status()
+        _status.pop('untracked', None)
+        _status.pop('detached', None)
+        return not (len(_status) == 1 and 'branch' in _status)
+
+    def valid(self):
+        if os.path.exists(self.path):
+            ec, output = self._run(['status'])
+            return ec == 0
+        return False
+
+    def remotes(self):
+        _remotes = {}
+        ec, output = self._run(['config', '--list'])
+        if ec == 0:
+            for l in output.split(os.linesep):
+                if l.startswith('remote'):
+                    ls = l.split('=')
+                    if len(ls) >= 2:
+                        rs = ls[0].split('.')
+                        if len(rs) == 3:
+                            r_name = rs[1]
+                            r_type = rs[2]
+                            if r_name not in _remotes:
+                                _remotes[r_name] = {}
+                            if r_type not in _remotes[r_name]:
+                                _remotes[r_name][r_type] = []
+                            _remotes[r_name][r_type] = '='.join(ls[1:])
+        return _remotes
+
+    def email(self):
+        _email = None
+        _name = None
+        ec, output = self._run(['config', '--list'])
+        if ec == 0:
+            for l in output.split(os.linesep):
+                if l.startswith('user.email'):
+                    ls = l.split('=')
+                    if len(ls) >= 2:
+                        _email = ls[1]
+                elif l.startswith('user.name'):
+                    ls = l.split('=')
+                    if len(ls) >= 2:
+                        _name = ls[1]
+        if _email is not None:
+            if _name is not None:
+                _email = '%s <%s>' % (_name, _email)
+            return _email
+        return None
+
+    def head(self):
+        hash = ''
+        ec, output = self._run(['log', '-n', '1'])
+        if ec == 0:
+            l1 = output.split(os.linesep)[0]
+            if l1.startswith('commit '):
+                hash = l1[len('commit '):]
+        return hash
diff --git a/version.py b/version.py
new file mode 100644
index 0000000..87e18c5
--- /dev/null
+++ b/version.py
@@ -0,0 +1,270 @@
+#
+# RTEMS Tools Project (http://www.rtems.org/)
+# Copyright 2010-2018,2023 Chris Johns (chrisj at rtems.org)
+# All rights reserved.
+#
+# This file is part of the RTEMS Tools package in 'rtems-tools'.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+
+#
+# Releasing RTEMS Tools
+# ---------------------
+#
+# Format:
+#
+#  The format is INI. The file requires a `[version`] section and a `revision`
+#  option:
+#
+#   [version]
+#   revision = <version-string>
+#
+#  The `<version-string>` has the `version` and `revision` delimited by a
+#  single `.`. An example file is:
+#
+#   [version]
+#   revision = 5.0.not_released
+#
+#  where the `version` is `5` and the revision is `0` and the package is not
+#  released. The label `not_released` is reversed to mean the package is not
+#  released. A revision string can contain extra characters after the
+#  `revision` number for example `5.0-rc1` or is deploying a package
+#  `5.0-nasa-cfs`
+#
+#  Packages can optionally add specialised sections to a version configuration
+#  files. These can be accessed via the:
+#
+#   load_release_settings: Return the items in a section
+#   load_release_setting: Return an item from a section
+#
+# User deployment:
+#
+#  Create a git archive and then add a suitable VERSION file to the top
+#  directory of the package. The package assumes your python executable is
+#  location in `bin` directory which is one below the top of the package's
+#  install prefix.
+#
+# RTEMS Release:
+#
+#  Set the values in the `rtems-version.ini` file. This is a shared file so
+#  packages and encouraged to add specific settings to other configuration
+#  files.
+#
+# Notes:
+#
+#  This module uses os.apth for paths and assumes all paths are in the host
+#  format.
+#
+
+from __future__ import print_function
+
+import itertools
+import os
+import sys
+
+try:
+    import configparser
+except ImportError:
+    import ConfigParser as configparser
+
+from . import git
+from . import rtems
+
+#
+# Default to an internal string.
+#
+_version = 'undefined'
+_revision = 'not_released'
+_version_str = '%s.%s' % (_version, _revision)
+_released = False
+_git = False
+_is_loaded = False
+
+
+def _top(ctx):
+    top = ctx.path
+    if top == None:
+        cts.fatal('no top path found')
+    return str(top)
+
+def _load_released_version_config(ctx):
+    '''Local worker to load a configuration file.'''
+    top = _top(ctx)
+    for ver in [os.path.join(top, 'VERSION')]:
+        if os.path.exists(os.path.join(ver)):
+            v = configparser.SafeConfigParser()
+            try:
+                v.read(os.path.host(ver))
+            except Exception as e:
+                raise ctx.fatal('invalid version config format: %s: %s' %
+                                (ver, e))
+            return ver, v
+    return None, None
+
+
+def _load_released_version(ctx):
+    '''Load the release data if present. If not found the package is not
+    released.
+
+    A release can be made by adding a file called `VERSION` to the top level
+    directory of a package. This is useful for user deploying a package and
+    making custom releases.
+    '''
+    global _version
+    global _revision
+    global _released
+    global _version_str
+    global _is_loaded
+
+    if not _is_loaded:
+        vc, v = _load_released_version_config(ctx)
+        if v is not None:
+            try:
+                ver_str = v.get('version', 'revision')
+            except Exception as e:
+                raise ctx.fatal('invalid version file: %s: %s' % (vc, e))
+            ver_split = ver_str.split('.')
+            if len(ver_split) < 2:
+                raise ctx.fatal('invalid version release value: %s: %s' %
+                                (vc, ver_str))
+            ver = ver_split[0]
+            rev = '.'.join(ver_split[1:])
+            try:
+                _version = int(ver)
+            except:
+                raise ctx.fatal('invalid version config value: %s: %s' %
+                                (vc, ver))
+            try:
+                _revision = int(''.join(
+                    itertools.takewhile(str.isdigit, str(rev))))
+            except Exception as e:
+                raise ctx.fatal('Invalid revision config value: %s: %s: %s' %
+                                (vc, rev, e))
+            if not 'not_released' in ver:
+                _released = True
+            _version_str = ver_str
+            _is_loaded = True
+    return _released
+
+
+def _load_git_version(ctx):
+    global _version
+    global _revision
+    global _git
+    global _version_str
+    repo = git.repo(ctx, _top(ctx))
+    if repo.valid():
+        head = repo.head()
+        if repo.dirty():
+            modified = 'modified'
+            revision_sep = '-'
+            sep = ' '
+        else:
+            modified = ''
+            revision_sep = ''
+            sep = ''
+        _revision = '%s%s%s' % (head[0:12], revision_sep, modified)
+        _version_str += ' (%s%s%s)' % (head[0:12], sep, modified)
+        _git = True
+    return _git
+
+
+def load_release_settings(ctx, section, error=False):
+    vc, v = _load_released_version_config(ctx)
+    items = []
+    if v is not None:
+        try:
+            items = v.items(section)
+        except Exception as e:
+            if not isinstance(error, bool):
+                error(e)
+            elif error:
+                raise ctx.fatal('Invalid config section: %s: %s: %s' %
+                                (vc, section, e))
+    return items
+
+
+def load_release_setting(ctx, section, option, raw=False, error=False):
+    vc, v = _load_released_version_config()
+    value = None
+    if v is not None:
+        try:
+            value = v.get(section, option, raw=raw)
+        except Exception as e:
+            if not isinstance(error, bool):
+                error(e)
+            elif error:
+                raise ctx.fatal('Invalid config section: %s: %s: %s.%s' %
+                                (vc, section, option, e))
+    return value
+
+
+def load_rtems_version_header(ctx, rtems_version, arch_bsp, incpaths):
+    global _version
+    global _revision
+    global _version_str
+    for inc in incpaths:
+        header = os.path.join(inc, 'rtems/score/cpuopts.h')
+        if os.path.exists(header):
+            try:
+                with open(header, 'r') as h:
+                    text = h.readlines()
+            except:
+                ctx.fatal('cannot read: ' + header)
+            for l in text:
+                ls = l.split()
+                if len(ls) == 3:
+                    if ls[1] == '__RTEMS_MAJOR__':
+                        _version = int(ls[2])
+                    elif ls[1] == '__RTEMS_REVISION__':
+                        _revision = int(ls[2])
+                    elif ls[1] == 'RTEMS_VERSION':
+                        _version_str = ls[2][1:-1]
+            _is_loaded = True
+            break
+
+def released(ctx):
+    return _load_released_version(ctx)
+
+
+def version_control(ctx):
+    return _load_git_version(ctx)
+
+
+def string(ctx):
+    _load_released_version(ctx)
+    _load_git_version(ctx)
+    return _version_str
+
+
+def version(ctx):
+    _load_released_version(ctx)
+    _load_git_version(ctx)
+    return _version
+
+
+def revision(ctx):
+    _load_released_version(ctx)
+    _load_git_version(ctx)
+    return _revision



More information about the vc mailing list