From d8b18385e88df29dd0ce38db484d1d7912ddea15 Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Sun, 22 Sep 2019 18:28:01 +0200 Subject: pro2cmake: Improve handling of requires clauses Change the grammar to parse and extract the whole expression that is present in the requires clause. Make sure to preprocess the parsed content as a condition, so that the value passed to map_condition and simplify_condition is valid and the functions can handle more complicated conditions like qtConfig() and if(). Wrap the final condition with an extra pair of parentheses, so that the negated condition is computed correctly. Handle the require clause in subdir projects, top level tests projects, regular test projects, as well as top level repo projects. Examples are not yet handled, because the would require some kind of CMake public api to e.g. query if a feature is enabled. Change-Id: I9af96cf022e47b66f24db3ca15d3803dda7a5d7c Reviewed-by: Qt CMake Build Bot Reviewed-by: Simon Hausmann --- util/cmake/pro2cmake.py | 53 +++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/util/cmake/pro2cmake.py b/util/cmake/pro2cmake.py index aad0d621f6..797844f7d5 100755 --- a/util/cmake/pro2cmake.py +++ b/util/cmake/pro2cmake.py @@ -49,6 +49,7 @@ import xml.etree.ElementTree as ET from argparse import ArgumentParser from textwrap import dedent +from textwrap import indent as textwrap_indent from itertools import chain from functools import lru_cache from shutil import copyfile @@ -1271,8 +1272,22 @@ class QmakeParser: Load = add_element("Load", pp.Keyword("load") + CallArgs("loaded")) Include = add_element("Include", pp.Keyword("include") + CallArgs("included")) Option = add_element("Option", pp.Keyword("option") + CallArgs("option")) + RequiresCondition = add_element("RequiresCondition", pp.originalTextFor(pp.nestedExpr())) + + def parse_requires_condition(s, l, t): + # The following expression unwraps the condition via the additional info + # set by originalTextFor. + condition_without_parentheses = s[t._original_start + 1 : t._original_end - 1] + + # And this replaces the colons with '&&' similar how it's done for 'Condition'. + condition_without_parentheses = ( + condition_without_parentheses.strip().replace(":", " && ").strip(" && ") + ) + return condition_without_parentheses + + RequiresCondition.setParseAction(parse_requires_condition) Requires = add_element( - "Requires", pp.Keyword("requires") + CallArgs("project_required_condition") + "Requires", pp.Keyword("requires") + RequiresCondition("project_required_condition") ) # ignore the whole thing... @@ -1598,7 +1613,7 @@ def handle_subdir( current_conditions=frozenset((*current_conditions, child_condition)), ) - def group_and_print_sub_dirs(indent: int = 0): + def group_and_print_sub_dirs(scope: Scope, indent: int = 0): # Simplify conditions, and group # subdirectories with the same conditions. grouped_sub_dirs = {} @@ -1652,6 +1667,9 @@ def handle_subdir( sub_dir_list_by_key.append(subdir_name) grouped_sub_dirs[condition_key] = sub_dir_list_by_key + # Print any requires() blocks. + cm_fh.write(expand_project_requirements(scope)) + # Print the groups. ind = spaces(indent) for condition_key in grouped_sub_dirs: @@ -1678,7 +1696,7 @@ def handle_subdir( handle_subdir_helper( scope, cm_fh, indent=indent, current_conditions=current_conditions, is_example=is_example ) - group_and_print_sub_dirs(indent=indent) + group_and_print_sub_dirs(scope, indent=indent) def sort_sources(sources: List[str]) -> List[str]: @@ -2352,19 +2370,22 @@ def write_statecharts(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0 cm_fh.write(")\n") -def expand_project_requirements(scope: Scope) -> str: +def expand_project_requirements(scope: Scope, skip_message: bool = False) -> str: requirements = "" for requirement in scope.get("_REQUIREMENTS"): original_condition = simplify_condition(map_condition(requirement)) - inverted_requirement = simplify_condition(f"NOT {map_condition(requirement)}") + inverted_requirement = simplify_condition(f"NOT ({map_condition(requirement)})") + if not skip_message: + message = f""" +{spaces(7)}message(NOTICE "Skipping the build as the condition \\"{original_condition}\\" is not met.")""" + else: + message = "" requirements += dedent( f"""\ - if({inverted_requirement}) - message(NOTICE "Skipping the build as the condition \\"{original_condition}\\" is not met.") + if({inverted_requirement}){message} return() endif() - - """ +""" ) return requirements @@ -2683,6 +2704,11 @@ def write_test(cm_fh: IO[str], scope: Scope, gui: bool = False, *, indent: int = for path in importpath: extra.append(f' "{path}"') + requires_content = expand_project_requirements(scope, skip_message=True) + if requires_content: + requires_content += "\n" + cm_fh.write(requires_content) + write_main_part( cm_fh, test_name, @@ -3131,16 +3157,21 @@ def handle_top_level_repo_tests_project(scope: Scope, cm_fh: IO[str]): else: qt_lib = "Tests_FIXME" + requires_content = expand_project_requirements(scope, skip_message=True) + if requires_content: + requires_content = f"\n\n{textwrap_indent(requires_content, spaces(3))}" + content = dedent( f"""\ if(NOT TARGET Qt::Test) cmake_minimum_required(VERSION {cmake_version_string}) project({qt_lib} VERSION 6.0.0 LANGUAGES C CXX) find_package(Qt6 ${{PROJECT_VERSION}} REQUIRED COMPONENTS BuildInternals Core SET_ME_TO_SOMETHING_USEFUL) - find_package(Qt6 ${{PROJECT_VERSION}} OPTIONAL_COMPONENTS SET_ME_TO_SOMETHING_USEFUL) + find_package(Qt6 ${{PROJECT_VERSION}} OPTIONAL_COMPONENTS SET_ME_TO_SOMETHING_USEFUL){requires_content} qt_set_up_standalone_tests_build() endif() - qt_build_tests()""" + qt_build_tests() +""" ) cm_fh.write(f"{content}") -- cgit v1.2.3