From 6e3fba8f4ac75645480bb12216f46eeb413c2936 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Fri, 18 Jan 2019 12:29:32 +0100 Subject: Say hello to qp5_tool.py qp5_tool.py is a developer helper tool modeled after the qt5_tool Perl script of Qt 5. It maintains a configuration file where per-directory values (build options, module subsets) can be given. Creating an up-to-date Build is then done by calling: python build_scripts/qp5_tool.py -c -p -b in each directory, Change-Id: Ifdcf414f7ff89512bb81a132c0bfd2e49275ed24 Reviewed-by: Friedemann Kleint Reviewed-by: Cristian Maureira-Fredes --- build_scripts/qp5_tool.py | 261 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) create mode 100644 build_scripts/qp5_tool.py diff --git a/build_scripts/qp5_tool.py b/build_scripts/qp5_tool.py new file mode 100644 index 000000000..aca8cf7f6 --- /dev/null +++ b/build_scripts/qp5_tool.py @@ -0,0 +1,261 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +from __future__ import print_function + +from argparse import ArgumentParser, RawTextHelpFormatter +import os +import re +import subprocess +import sys +import warnings + +desc = """ +Utility script for working with Qt for Python. + +Feel free to extend! + +qp5_tool.py can be configured by creating a configuration file +in the format key=value: + "%CONFIGFILE%" + +It is possible to use repository-specific values +by adding a key postfixed by a dash and the repository folder base name, eg: +Modules-pyside-setup512=Core,Gui,Widgets,Network,Test + +Configuration keys: +Modules Comma separated list of modules to be built + (for --module-subset=) +BuildArguments Arguments to setup.py +Python Python executable (Use python_d for debug builds on Windows) + +Arbitrary keys can be defined and referenced by $(name): + +MinimalModules=Core,Gui,Widgets,Network,Test +Modules=$(MinimalModules),Multimedia +Modules-pyside-setup-minimal=$(MinimalModules) +""" + +def which(needle): + """Perform a path search""" + needles = [needle] + if is_windows: + for ext in ("exe", "bat", "cmd"): + needles.append("{}.{}".format(needle, ext)) + + for path in os.environ.get("PATH", "").split(os.pathsep): + for n in needles: + binary = os.path.join(path, n) + if os.path.isfile(binary): + return binary + return None + +def execute(args): + """Execute a command and print to log""" + log_string = '[{}] {}'.format(os.path.basename(os.getcwd()), ' '.join(args)) + print(log_string) + exit_code = subprocess.call(args) + if exit_code != 0: + raise RuntimeError('FAIL({}): {}'.format(exit_code, log_string)) + +def run_git(args): + """Run git in the current directory and its submodules""" + args.insert(0, git) # run in repo + execute(args) # run for submodules + module_args = [git, "submodule", "foreach"] + module_args.extend(args) + execute(module_args) + +def expand_reference(dict, value): + """Expand references to other keys in config files $(name) by value.""" + pattern = re.compile(r"\$\([^)]+\)") + while True: + match = pattern.match(value) + if not match: + break + key = match.group(0)[2:-1] + value = value[:match.start(0)] + dict[key] + value[match.end(0):] + return value + +""" +Config file handling, cache and read function +""" + +config_dict = {} + +def read_config_file(fileName): + global config_dict + keyPattern = re.compile(r'^\s*([A-Za-z0-9\_\-]+)\s*=\s*(.*)$') + with open(config_file) as f: + while True: + line = f.readline().rstrip() + if not line: + break + match = keyPattern.match(line) + if match: + key = match.group(1) + value = match.group(2) + while value.endswith('\\'): + value = value.rstrip('\\') + value += f.readline().rstrip() + config_dict[key] = expand_reference(config_dict, value) + +def read_tool_config(key): + """ + Read a value from the '$HOME/.qp5_tool' configuration file. When given + a key 'key' for the repository directory '/foo/qt-5', check for the + repo-specific value 'key-qt5' and then for the general 'key'. + """ + if not config_dict: + read_config_file(config_file) + repo_value = config_dict.get(key + '-' + base_dir) + return repo_value if repo_value else config_dict.get(key) + +def read_config_build_arguments(): + value = read_tool_config('BuildArguments') + if value: + return re.split(r'\s+', value) + return default_build_args; + +def read_config_modules_argument(): + value = read_tool_config('Modules') + if value and value != '' and value != 'all': + return '--module-subset=' + value + return None + +def read_config_python_binary(): + binary = read_tool_config('Python') + return binary if binary else 'python' + +def get_config_file(): + home = os.getenv('HOME') + if is_windows: + # Set a HOME variable on Windows such that scp. etc. + # feel at home (locating .ssh). + if not home: + home = os.getenv('HOMEDRIVE') + os.getenv('HOMEPATH') + os.environ['HOME'] = home + user = os.getenv('USERNAME') + config_file = os.path.join(os.getenv('APPDATA'), config_file_name) + else: + user = os.getenv('USER') + config_dir = os.path.join(home, '.config') + if os.path.exists(config_dir): + config_file = os.path.join(config_dir, config_file_name) + else: + config_file = os.path.join(home, '.' + config_file_name) + return config_file + +def get_options(desc): + parser = ArgumentParser(description=desc, formatter_class=RawTextHelpFormatter) + parser.add_argument('--reset', '-r', action='store_true', + help='Git reset hard to upstream state') + parser.add_argument('--clean', '-c', action='store_true', + help='Git clean') + parser.add_argument('--pull', '-p', action='store_true', + help='Git pull') + parser.add_argument('--build', '-b', action='store_true', + help='Build (configure + build)') + parser.add_argument('--make', '-m', action='store_true', help='Make') + parser.add_argument('--Make', '-M', action='store_true', + help='cmake + Make (continue broken build)') + parser.add_argument('--version', '-v', action='version', version='%(prog)s 1.0') + + return parser.parse_args() + +if __name__ == '__main__': + + git = None + base_dir = None + default_build_args = ['--build-tests', '--skip-docs', '--quiet'] + is_windows = sys.platform == 'win32' + config_file_name = 'qp5_tool.conf' + config_file = None + user = None + default_config_file = """ + Modules=Core,Gui,Widgets,Network,Test,Qml,Quick,Multimedia,MultimediaWidgets + BuildArguments={} + # Python executable (python_d for debug builds) + Python=python + """ + + config_file = get_config_file() + desc = desc.replace('%CONFIGFILE%', config_file) + options = get_options(desc) + + git = which('git') + if git is None: + warnings.warn('Unable to find git', RuntimeWarning) + sys.exit(-1) + + if not os.path.exists(config_file): + print('Create initial config file ', config_file, " ..") + with open(config_file, 'w') as f: + f.write(default_config_file.format(' '.join(default_build_args))) + + while not os.path.exists('.gitmodules'): + cwd = os.getcwd() + if cwd == '/' or (is_windows and len(cwd) < 4): + warnings.warn('Unable to find git root', RuntimeWarning) + sys.exit(-1) + os.chdir(os.path.dirname(cwd)) + + base_dir = os.path.basename(os.getcwd()) + + if options.clean: + run_git(['clean', '-dxf']) + + if options.reset: + run_git(['reset', '--hard', '@{upstream}']) + + if options.pull: + run_git(['pull', '--rebase']) + + if options.build or options.make or options.Make: + arguments = [read_config_python_binary(), 'setup.py', 'install'] + arguments.extend(read_config_build_arguments()) + if options.make or options.Make: + arguments.extend(['--reuse-build', '--ignore-git']) + if not options.Make: + arguments.append('--skip-cmake') + modules = read_config_modules_argument() + if modules: + arguments.append(modules) + execute(arguments) + sys.exit(0) -- cgit v1.2.3