summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChris Johns <chrisj@rtems.org>2023-04-14 13:40:43 +1000
committerChris Johns <chrisj@rtems.org>2023-04-14 13:40:43 +1000
commitbbb77343a96302afbd78d589fa322cd56b98f1e8 (patch)
treeab8ce7808015842e9c1250f3cb87df5f0aeb8622
parentrtems: Fix IFLAGS and add arch_bsp_name (diff)
downloadrtems_waf-bbb77343a96302afbd78d589fa322cd56b98f1e8.tar.bz2
Add version and git support for apps to use
-rw-r--r--git.py226
-rw-r--r--version.py270
2 files changed, 496 insertions, 0 deletions
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@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@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