From d65283ebd01c221d0f03a2873c19dc3723de536c Mon Sep 17 00:00:00 2001 From: Alexandru Croitor Date: Mon, 27 Jul 2020 11:10:48 +0200 Subject: CMake: Teach pro2cmake to convert load(qt_app) Changes pro2cmake to handle load(qt_app) projects and write out qt_internal_add_app calls. Also adds handling of macOS and Windows specific resource files for Qt apps only. Task-number: QTBUG-85757 Change-Id: I994d8d19ab2ae366a985cab7894b97d6a278a56f Reviewed-by: Cristian Adam --- util/cmake/pro2cmake.py | 246 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 233 insertions(+), 13 deletions(-) (limited to 'util') diff --git a/util/cmake/pro2cmake.py b/util/cmake/pro2cmake.py index 823be0d07b..37f0b845f5 100755 --- a/util/cmake/pro2cmake.py +++ b/util/cmake/pro2cmake.py @@ -325,6 +325,7 @@ def set_up_cmake_api_calls(): api[1]["qt_add_module"] = "add_qt_module" api[1]["qt_add_plugin"] = "add_qt_plugin" api[1]["qt_add_tool"] = "add_qt_tool" + api[2]["qt_internal_add_app"] = "qt_internal_add_app" api[1]["qt_add_test"] = "add_qt_test" api[1]["qt_add_test_helper"] = "add_qt_test_helper" api[1]["qt_add_manual_test"] = "add_qt_manual_test" @@ -341,6 +342,7 @@ def set_up_cmake_api_calls(): api[2]["qt_add_module"] = "qt_add_module" api[2]["qt_add_plugin"] = "qt_internal_add_plugin" api[2]["qt_add_tool"] = "qt_add_tool" + api[2]["qt_internal_add_app"] = "qt_internal_add_app" api[2]["qt_add_test"] = "qt_add_test" api[2]["qt_add_test_helper"] = "qt_add_test_helper" api[2]["qt_add_manual_test"] = "qt_add_manual_test" @@ -956,6 +958,7 @@ class Scope(object): self._parent_include_line_no = parent_include_line_no self._is_public_module = False self._has_private_module = False + self._is_internal_qt_app = False def __repr__(self): return ( @@ -995,6 +998,16 @@ class Scope(object): def has_private_module(self) -> bool: return self._has_private_module + @property + def is_internal_qt_app(self) -> bool: + is_app = self._is_internal_qt_app + current_scope = self + while not is_app and current_scope.parent: + current_scope = current_scope.parent + is_app = current_scope.is_internal_qt_app + + return is_app + def can_merge_condition(self): if self._condition == "else": return False @@ -2086,7 +2099,7 @@ def write_3rd_party_include_paths( for i in includes: # CMake generator expressions don't seem to like relative paths. # Make them absolute relative to the source dir. - if not os.path.isabs(i) and not i.startswith("$"): + if is_path_relative_ish(i): i = f"${{CMAKE_CURRENT_SOURCE_DIR}}/{i}" i = f"$" processed_includes.append(i) @@ -2465,9 +2478,59 @@ def write_repc_files(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0) cm_fh.write(")\n") -def write_generic_cmake_command(cm_fh: IO[str], command_name: str, arguments: List[str]): +def write_generic_cmake_command(cm_fh: IO[str], command_name: str, arguments: List[str], + indent: int = 0): + ind = spaces(indent) arguments_str = " ".join(arguments) - cm_fh.write(f"{command_name}({arguments_str})\n") + cm_fh.write(f"{ind}{command_name}({arguments_str})\n") + + +def write_set_target_properties(cm_fh: IO[str], + targets: List[str], + properties: List[str], + indent: int = 0): + ind = spaces(indent) + command_name = 'set_target_properties' + arguments_ind = spaces(indent + 1) + + prop_pairs = [(properties[i] + ' ' + properties[i + 1]) for i in range(0, len(properties), 2)] + properties_str = f"\n{arguments_ind}" + f"\n{arguments_ind}".join(prop_pairs) + + if len(targets) == 1: + targets_str = targets[0] + ' ' + else: + targets_str = f"\n{arguments_ind}" + f"\n{arguments_ind}".join(targets) + f"\n{arguments_ind}" + + cm_fh.write(f"{ind}{command_name}({targets_str}PROPERTIES{properties_str}\n{ind})\n") + + +def write_set_source_files_properties(cm_fh: IO[str], + files: List[str], + properties: List[str], + indent: int = 0): + ind = spaces(indent) + command_name = 'set_source_files_properties' + arguments_ind = spaces(indent + 1) + + prop_pairs = [(properties[i] + ' ' + properties[i + 1]) for i in range(0, len(properties), 2)] + properties_str = f"\n{arguments_ind}" + f"\n{arguments_ind}".join(prop_pairs) + + if len(files) == 1: + targets_str = files[0] + ' ' + else: + targets_str = f"\n{arguments_ind}" + f"\n{arguments_ind}".join(files) + f"\n{arguments_ind}" + + cm_fh.write(f"{ind}{command_name}({targets_str}PROPERTIES{properties_str}\n{ind})\n") + + +def write_target_sources(cm_fh: IO[str], + target: str, + sources: List[str], + visibility: str = 'PRIVATE', + indent: int = 0): + command_name = 'target_sources' + header = f'{command_name}({target} {visibility}\n' + write_list(cm_fh, sources, '', indent, footer=f')', header=header) def expand_project_requirements(scope: Scope, skip_message: bool = False) -> str: @@ -2665,13 +2728,7 @@ def write_wayland_part(cm_fh: IO[str], target: str, scope: Scope, indent: int = if len(client_sources) == 0 and len(server_sources) == 0: return - condition = "ON" - if scope.total_condition: - condition = map_to_cmake_condition(scope.total_condition) - - if condition != "ON": - cm_fh.write(f"\n{spaces(indent)}if({condition})\n") - indent += 1 + condition, indent = write_scope_condition_begin(cm_fh, scope, indent=indent) if len(client_sources) != 0: cm_fh.write(f"\n{spaces(indent)}qt6_generate_wayland_protocol_client_sources({target}\n") @@ -2687,9 +2744,133 @@ def write_wayland_part(cm_fh: IO[str], target: str, scope: Scope, indent: int = ) cm_fh.write(f"{spaces(indent)})\n") + write_scope_condition_end(cm_fh, condition, indent=indent) + + +def write_scope_condition_begin(cm_fh: IO[str], scope: Scope, indent: int = 0) -> Tuple[str, int]: + condition = "ON" + if scope.total_condition: + condition = map_to_cmake_condition(scope.total_condition) + + if condition != "ON": + cm_fh.write(f"\n{spaces(indent)}if({condition})\n") + indent += 1 + + return condition, indent + + +def write_scope_condition_end(cm_fh: IO[str], condition: str, indent: int = 0) -> int: if condition != "ON": indent -= 1 - cm_fh.write(f"\n{spaces(indent)}endif()\n") + cm_fh.write(f"{spaces(indent)}endif()\n") + return indent + + +def is_path_relative_ish(path: str) -> bool: + if not os.path.isabs(path) and not path.startswith("$"): + return True + return False + + +def absolutify_path(path: str, base_dir: str = '${CMAKE_CURRENT_SOURCE_DIR}') -> str: + if not path: + return path + if is_path_relative_ish(path): + path = posixpath.join(base_dir, path) + return path + + +def write_version_part(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0): + if scope.is_internal_qt_app: + version_value = scope.get_string("VERSION") + if version_value: + version_value = re.sub(r"\$\${QT_VERSION\}", "${PROJECT_VERSION}", version_value) + target_description = scope.expandString("QMAKE_TARGET_DESCRIPTION") + + if version_value or target_description: + condition, indent = write_scope_condition_begin(cm_fh, scope, indent=indent) + + properties = [] + if version_value: + properties.extend(['QT_TARGET_VERSION', f'"{version_value}"']) + if target_description: + properties.extend(['QT_TARGET_DESCRIPTION', f'"{target_description}"']) + + if properties: + write_set_target_properties(cm_fh, [target], properties, indent=indent) + + write_scope_condition_end(cm_fh, condition, indent=indent) + + +def write_darwin_part(cm_fh: IO[str], target: str, scope: Scope, main_scope_target_name: str = '', + indent: int = 0): + if scope.is_internal_qt_app: + # Embed custom provided Info.plist file. + info_plist = scope.expandString("QMAKE_INFO_PLIST") + info_plist = absolutify_path(info_plist) + + icon_path = scope.expandString("ICON") + icon_basename = '' + + new_output_name = None + current_scope_output_name = scope.TARGET + if current_scope_output_name != main_scope_target_name: + new_output_name = current_scope_output_name + + if icon_path: + icon_basename = os.path.basename(icon_path) + + if info_plist or icon_path or new_output_name: + condition, indent = write_scope_condition_begin(cm_fh, scope, indent=indent) + + properties = [] + if info_plist: + properties.extend(['MACOSX_BUNDLE_INFO_PLIST', f'"{info_plist}"']) + properties.extend(['MACOSX_BUNDLE', 'TRUE']) + if icon_path: + properties.extend(['MACOSX_BUNDLE_ICON_FILE', f'"{icon_basename}"']) + + if new_output_name: + properties.extend(['OUTPUT_NAME', f'"{new_output_name}"']) + + if properties: + write_set_target_properties(cm_fh, [target], properties, indent=indent) + if icon_path: + source_properties = ['MACOSX_PACKAGE_LOCATION', 'Resources'] + write_set_source_files_properties(cm_fh, [icon_path], source_properties, + indent=indent) + write_target_sources(cm_fh, target, [icon_path], indent=indent) + + write_scope_condition_end(cm_fh, condition, indent=indent) + + +def write_windows_part(cm_fh: IO[str], target: str, scope: Scope, indent: int = 0): + if scope.is_internal_qt_app: + # Handle CONFIG += console assignments. + is_console = "console" in scope.get("CONFIG") + rc_file = scope.expandString("RC_FILE") + rc_file = absolutify_path(rc_file) + rc_icons = scope.expandString("RC_ICONS") + rc_icons = absolutify_path(rc_icons) + + if is_console or rc_file or rc_icons: + condition, indent = write_scope_condition_begin(cm_fh, scope, indent=indent) + + properties = [] + if is_console: + properties.extend(['WIN32_EXECUTABLE', 'FALSE']) + + if rc_file: + properties.extend(['QT_TARGET_WINDOWS_RC_FILE', f'"{rc_file}"']) + + if rc_icons: + properties.extend(['QT_TARGET_RC_ICONS', f'"{rc_icons}"']) + + if properties: + write_set_target_properties(cm_fh, [target], + properties, indent=indent) + + write_scope_condition_end(cm_fh, condition, indent=indent) def handle_source_subtractions(scopes: List[Scope]): @@ -2945,6 +3126,12 @@ def write_main_part( write_wayland_part(cm_fh, name, scopes[0], indent) + write_windows_part(cm_fh, name, scopes[0], indent) + + write_darwin_part(cm_fh, name, scopes[0], main_scope_target_name=name, indent=indent) + + write_version_part(cm_fh, name, scopes[0], indent) + if "warn_off" in scope.get("CONFIG"): write_generic_cmake_command(cm_fh, "qt_disable_warnings", [name]) @@ -2965,6 +3152,9 @@ def write_main_part( c.reset_visited_keys() write_android_part(cm_fh, name, c, indent=indent) write_wayland_part(cm_fh, name, c, indent=indent) + write_windows_part(cm_fh, name, c, indent=indent) + write_darwin_part(cm_fh, name, c, main_scope_target_name=name, indent=indent) + write_version_part(cm_fh, name, c, indent=indent) write_extend_target(cm_fh, name, c, target_ref=target_ref, indent=indent) write_simd_part(cm_fh, name, c, indent=indent) @@ -3065,12 +3255,13 @@ def write_generic_library(cm_fh: IO[str], scope: Scope, *, indent: int = 0) -> s return target_name -def forward_target_info(scope: Scope, extra: [str]): + +def forward_target_info(scope: Scope, extra: [str], skip: Optional[Dict[str]] = None): s = scope.get_string("QMAKE_TARGET_PRODUCT") if s: extra.append(f"TARGET_PRODUCT \"{s}\"") s = scope.get_string("QMAKE_TARGET_DESCRIPTION") - if s: + if s and (not skip or 'QMAKE_TARGET_DESCRIPTION' not in skip): extra.append(f"TARGET_DESCRIPTION \"{s}\"") s = scope.get_string("QMAKE_TARGET_COMPANY") if s: @@ -3079,6 +3270,7 @@ def forward_target_info(scope: Scope, extra: [str]): if s: extra.append(f"TARGET_COPYRIGHT \"{s}\"") + def write_module(cm_fh: IO[str], scope: Scope, *, indent: int = 0) -> str: module_name = scope.TARGET if not module_name.startswith("Qt"): @@ -3171,6 +3363,30 @@ def write_tool(cm_fh: IO[str], scope: Scope, *, indent: int = 0) -> str: return tool_name, "${target_name}" +def write_qt_app(cm_fh: IO[str], scope: Scope, *, indent: int = 0) -> str: + app_name = scope.TARGET + + extra = [] + + target_info_skip = {} + target_info_skip['QMAKE_TARGET_DESCRIPTION'] = True + forward_target_info(scope, extra, target_info_skip) + + write_main_part( + cm_fh, + app_name, + "App", + get_cmake_api_call("qt_internal_add_app"), + scope, + indent=indent, + known_libraries={"Qt::Core"}, + extra_lines=extra, + extra_keys=["CONFIG"], + ) + + return app_name + + def write_test(cm_fh: IO[str], scope: Scope, gui: bool = False, *, indent: int = 0) -> str: test_name = scope.TARGET assert test_name @@ -3809,6 +4025,10 @@ def handle_app_or_lib( elif "qt_tool" in scope.get("_LOADED"): assert not is_example target, target_ref = write_tool(cm_fh, scope, indent=indent) + elif "qt_app" in scope.get("_LOADED"): + assert not is_example + scope._is_internal_qt_app = True + target = write_qt_app(cm_fh, scope, indent=indent) else: if "testcase" in config or "testlib" in config or "qmltestcase" in config: assert not is_example -- cgit v1.2.3