All of lore.kernel.org
 help / color / mirror / Atom feed
From: Cleber Rosa <crosa@redhat.com>
To: qemu-devel@nongnu.org
Cc: qemu-block@nongnu.org, Max Reitz <mreitz@redhat.com>,
	Kevin Wolf <kwolf@redhat.com>, John Snow <jsnow@redhat.com>,
	Jing Liu <liujbjl@linux.vnet.ibm.com>,
	Eric Blake <eblake@redhat.com>, Cleber Rosa <crosa@redhat.com>
Subject: [Qemu-devel] [PATCH 1/3] scripts: introduce buildconf.py
Date: Thu, 20 Jul 2017 23:47:28 -0400	[thread overview]
Message-ID: <20170721034730.25612-2-crosa@redhat.com> (raw)
In-Reply-To: <20170721034730.25612-1-crosa@redhat.com>

scripts/buildconf.py is a command line utility (but also can be used
as a Python module) that introspects the build configuration.

It uses the generated host level config-host.mak to obtain the general
build configuration, and optionally, also target specific
config-target.mak and config-devices.mak.  It does not attempt to
implement a Makefile parser, but instead relies on "make" itself to
parse those files and output the queried variable.

It requires a build tree that has been both configured and built.  By
default, for convenience, it will selected a default target, which can
be displayed and overriden.  A few examples follow.

To get the TLS priority (a host level configuration), one would run:

 $ ./scripts/buildconf.py CONFIG_TLS_PRIORITY
 NORMAL

To get a configuration from the default target devices:

 $ ./scripts/buildconf.py CONFIG_PARALLEL
 y

If one is not interested in the actual value, but whether a given
feature is enabled, the '-c|--check' option can be used:

 $ ./scripts/buildconf.py -c CONFIG_PARALLEL; echo $?
 0

And for checking a target different than the default one:

 $ ./scripts/buildconf.py -c CONFIG_PARALLEL arm-softmmu; echo $?
 255

Signed-off-by: Cleber Rosa <crosa@redhat.com>
---
 scripts/buildconf.py | 278 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 278 insertions(+)
 create mode 100755 scripts/buildconf.py

diff --git a/scripts/buildconf.py b/scripts/buildconf.py
new file mode 100755
index 0000000..0eee6d7
--- /dev/null
+++ b/scripts/buildconf.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python
+#
+# QEMU build configuration introspection utilty
+#
+# Copyright (C) 2017 Red Hat Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, see <http://www.gnu.org/licenses/>.
+#
+# Authors:
+#  Cleber Rosa <crosa@redhat.com>
+
+"""
+QEMU's build configuration is recorded in both config-host.mak and
+config-host.h, as is specific target configuration expressed in
+<target>/config-{target,devices}.{mak,h}.
+
+This module relies on the .mak files, as they contain a bit more
+information than the .h files.
+
+While it would be possible to write a simple Makefile parser capable to
+handle the variable assignments, this would impose limitations on this
+script and introduce breakages if the build scripts start using
+functionality not expected here.
+
+The approach chosen was one that is definitely slower at runtime but
+is more reliable.  Temporary Makefiles that include config-host.mak,
+and optionally the target specific config-target.mak and
+config-devices.mak files, and print the desired configuration to
+stdout.  As long as the basic premises of a global config-host.mak,
+and target specific config-target.mak and config-devices.mak is kept,
+this tool should be able to keep up with any style or feature chances.
+"""
+
+from __future__ import print_function
+
+import optparse
+import os
+import subprocess
+import sys
+import tempfile
+
+
+TEMPLATE = """
+include {build_prefix}/config-host.mak
+{target_specific}
+
+all:
+	@echo $({conf})
+"""
+
+TARGET_TEMPLATE = """
+include {build_prefix}/{target}/config-target.mak
+include {build_prefix}/{target}/config-devices.mak
+"""
+
+
+class InvalidTarget(Exception):
+    """
+    Target chosen is not present in the current build tree configuration
+    """
+
+
+def get_build_root():
+    """
+    Returns the absolute location of the root of the build tree
+
+    This has been tested from build(only) trees, and works fine when
+    it's executed as a command line tool.
+
+    If this is used as a Python module, it will really depend on how
+    the module is imported.  If the build tree "scripts" directory is
+    added to the import path, this will work properly.  If the current
+    working directory is the "scripts" directory itself, and no
+    explicit import path is added, it will only work when building
+    from the source tree, and will *not* work when the build tree is
+    different from the source tree.
+
+    A longer explanation on this caveat: the Python import
+    implementation will look for a matching module in the current
+    working directory.  Python's current working directory,
+    os.getcwd(), is really like getcwd(3), and not like
+    os.getenv("PWD").  Because the scripts directory is linked to the
+    source tree, os.getcwd() returns the source tree location instead.
+    """
+    return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+def is_build_root_configured():
+    """
+    Checks if the build root has been configured
+
+    In theory, this only makes sense for in-tree builds, because the
+    out-of-tree build directory, including the link to the scripts
+    directory containing this script, will only exist after a
+    successful "./configure" execution.
+
+    Either way, the check is still valid for the main source of
+    build configuration, that is, the existence of 'config-host.mak'
+    """
+    return os.path.isfile(os.path.join(get_build_root(), 'config-host.mak'))
+
+
+def get_default_target():
+    """
+    Returns the default target on the current build tree
+
+    The approach used here is to look for a "-softmmu" target that
+    matches "ARCH".  If found, that is the best choice for a default
+    target.
+
+    If not found, the first "-softmmu" target found is considered the
+    next best choice for a default target.
+
+    As a fallback if no "-softmmu" target exists, the first entry on
+    the target list is returned.
+
+    :returns: a target name or None if no target is configured
+    """
+    targets = get_targets()
+    if not targets:
+        return None
+    arch = get_build_conf("ARCH", None)
+    first_choice = "%s-softmmu" % arch
+    if first_choice in targets:
+        return first_choice
+    else:
+        softmmu_targets = [t for t in targets if t.endswith("-softmmu")]
+        if softmmu_targets:
+            return softmmu_targets[0]
+        else:
+            return targets[0]
+
+
+def get_build_conf(conf, target=None):
+    """
+    Returns the value of a given Makefile variable
+
+    :param conf: the configuration name, which really must be a
+                 Makefile variable in either the host or target .mak
+                 files
+    :param target: the name of a valid target in the current build tree.
+                   it must match the name of a target dir, such as
+                   'x86_64-softmmu' or 'i386-linux-user'.
+    :returns: the raw output or None
+    :rtype: str or None
+    """
+    build_prefix = get_build_root()
+
+    if target is None:
+        target_specific = ''
+    else:
+        if target not in get_targets():
+            raise InvalidTarget
+        target_specific = TARGET_TEMPLATE.format(build_prefix=build_prefix,
+                                                 target=target)
+
+    mak_fd, mak_path = tempfile.mkstemp()
+    os.write(mak_fd, TEMPLATE.format(build_prefix=build_prefix,
+                                     target_specific=target_specific,
+                                     conf=conf))
+    proc = subprocess.Popen(['make', '-f', mak_path],
+                            stdout=subprocess.PIPE,
+                            stderr=subprocess.PIPE)
+    ret = proc.wait()
+    os.unlink(mak_path)
+    if (ret == 0):
+        return proc.stdout.read().strip()
+
+
+def is_enabled(conf, target=None):
+    """
+    Checks wether a given feature is enabled in the build configuration
+
+    The Makefile variables default to using 'y' when they are enabled,
+    and are just missing (instead of set no 'n') when they're not enabled.
+    Even if a given variable is set, for instance in the case of TARGET_DIRS,
+    it will not be considered enabled by this function unless its content is
+    'y'.  For variables that are known to not contain 'y', please resort to
+    using get_build_conf() and parsing its output for meaningful value.
+
+    :param conf: the configuration name, which really must be a
+                 Makefile variable in either the host or target .mak
+                 files
+    :param target: the name of a valid target in the current build tree.
+                   it must match the name of a target dir, such as
+                   'x86_64-softmmu' or 'i386-linux-user'.
+    :returns: the raw output or None
+    :rtype: str or None
+    """
+    build_conf = get_build_conf(conf, target=target)
+    if build_conf == 'y':
+        return True
+    return False
+
+
+def get_targets():
+    """
+    Returns the list of targets currently configured
+
+    :rtype: list
+    """
+    targets = get_build_conf('TARGET_DIRS', None)
+    if targets is not None:
+        return targets.split()
+
+
+if __name__ == '__main__':
+    class Parser(optparse.OptionParser):
+
+        def __init__(self):
+            optparse.OptionParser.__init__(
+                self,
+                usage=('%prog [options] CONFIG [TARGET]\n\n'
+                       'CONFIG is the build configuration variable name\n'
+                       'TARGET is auto selected if not explicitly set'))
+            self.add_option('-c', '--check', action='store_true',
+                            help=('Checks if the build configuration option is '
+                                  'set to "y".  This causes this tool to be silent '
+                                  'and return only a status code of either 0 (if '
+                                  'configuration is set) or non-zero otherwise.'))
+            self.add_option('-n', '--no-default-target', action='store_true',
+                            help=('Do not attempt to use a default target if one '
+                                  'was not explicitly given in the command line'))
+            self.add_option('--print-target', action='store_true',
+                            help=('Also prints the selected target'))
+
+
+    class App(object):
+
+        def __init__(self):
+            self.target = None
+            self.parser = Parser()
+            self._parse()
+
+        def _parse(self):
+            self.opts, self.args = self.parser.parse_args()
+            args_len = len(self.args)
+            if (args_len < 1 or args_len > 2):
+                self.parser.print_help()
+                sys.exit(0)
+            elif args_len == 2:
+                self.target = self.args[1]
+            else:
+                if not self.opts.no_default_target:
+                    self.target = get_default_target()
+
+        def run(self):
+            if self.opts.print_target:
+                print("TARGET:", self.target)
+            config = self.args[0]
+            if self.opts.check:
+                result = is_enabled(config, self.target)
+                if result:
+                    sys.exit(0)
+                else:
+                    sys.exit(-1)
+            else:
+                conf = get_build_conf(config, self.target)
+                if conf:
+                    print(conf)
+                    sys.exit(0)
+                else:
+                    sys.exit(-1)
+
+
+    app = App()
+    app.run()
-- 
2.9.4

  reply	other threads:[~2017-07-21  3:48 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-07-21  3:47 [Qemu-devel] [PATCH 0/3] build configuration query tool and conditional (qemu-io)test skip Cleber Rosa
2017-07-21  3:47 ` Cleber Rosa [this message]
2017-07-21 14:00   ` [Qemu-devel] [PATCH 1/3] scripts: introduce buildconf.py Eric Blake
2017-07-21 14:07     ` Cleber Rosa
2017-07-21  3:47 ` [Qemu-devel] [PATCH 2/3] qemu-iotests: add _require_feature() function Cleber Rosa
2017-07-21  3:47 ` [Qemu-devel] [PATCH 3/3] qemu-iotests: require CONFIG_LINUX_AIO for test 087 Cleber Rosa
2017-07-24  6:44   ` Jing Liu
2017-07-25 15:45     ` [Qemu-devel] [Qemu-block] " Stefan Hajnoczi
2017-07-25 15:48       ` Daniel P. Berrange
2017-07-26  8:55         ` Jing Liu
2017-07-26  9:49           ` Daniel P. Berrange
2017-07-21  4:39 ` [Qemu-devel] [PATCH 0/3] build configuration query tool and conditional (qemu-io)test skip no-reply
2017-07-21 12:33 ` Stefan Hajnoczi
2017-07-21 13:49   ` Cleber Rosa
2017-08-08  8:01     ` Markus Armbruster
2017-07-21 14:01   ` Daniel P. Berrange
2017-07-21 14:21     ` Cleber Rosa
2017-07-25 15:49       ` Stefan Hajnoczi
2017-07-25 16:16         ` Cleber Rosa
2017-07-25 16:24           ` Daniel P. Berrange
2017-07-25 16:47             ` Cleber Rosa
2017-07-26 17:58           ` Stefan Hajnoczi
2017-07-26 18:24             ` Cleber Rosa
2017-07-27 13:41               ` Stefan Hajnoczi
2017-08-08  8:06                 ` Markus Armbruster
2017-08-08 12:44                   ` Stefan Hajnoczi
2017-08-08 14:52                     ` Markus Armbruster
2017-08-09 10:30                       ` Stefan Hajnoczi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170721034730.25612-2-crosa@redhat.com \
    --to=crosa@redhat.com \
    --cc=eblake@redhat.com \
    --cc=jsnow@redhat.com \
    --cc=kwolf@redhat.com \
    --cc=liujbjl@linux.vnet.ibm.com \
    --cc=mreitz@redhat.com \
    --cc=qemu-block@nongnu.org \
    --cc=qemu-devel@nongnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.