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 <cristian.adam@qt.io>
This commit is contained in:
Alexandru Croitor 2020-07-27 11:10:48 +02:00
parent b3b1e47378
commit d65283ebd0

View File

@ -325,6 +325,7 @@ def set_up_cmake_api_calls():
api[1]["qt_add_module"] = "add_qt_module" api[1]["qt_add_module"] = "add_qt_module"
api[1]["qt_add_plugin"] = "add_qt_plugin" api[1]["qt_add_plugin"] = "add_qt_plugin"
api[1]["qt_add_tool"] = "add_qt_tool" 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"] = "add_qt_test"
api[1]["qt_add_test_helper"] = "add_qt_test_helper" api[1]["qt_add_test_helper"] = "add_qt_test_helper"
api[1]["qt_add_manual_test"] = "add_qt_manual_test" 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_module"] = "qt_add_module"
api[2]["qt_add_plugin"] = "qt_internal_add_plugin" api[2]["qt_add_plugin"] = "qt_internal_add_plugin"
api[2]["qt_add_tool"] = "qt_add_tool" 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"] = "qt_add_test"
api[2]["qt_add_test_helper"] = "qt_add_test_helper" api[2]["qt_add_test_helper"] = "qt_add_test_helper"
api[2]["qt_add_manual_test"] = "qt_add_manual_test" 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._parent_include_line_no = parent_include_line_no
self._is_public_module = False self._is_public_module = False
self._has_private_module = False self._has_private_module = False
self._is_internal_qt_app = False
def __repr__(self): def __repr__(self):
return ( return (
@ -995,6 +998,16 @@ class Scope(object):
def has_private_module(self) -> bool: def has_private_module(self) -> bool:
return self._has_private_module 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): def can_merge_condition(self):
if self._condition == "else": if self._condition == "else":
return False return False
@ -2086,7 +2099,7 @@ def write_3rd_party_include_paths(
for i in includes: for i in includes:
# CMake generator expressions don't seem to like relative paths. # CMake generator expressions don't seem to like relative paths.
# Make them absolute relative to the source dir. # 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"${{CMAKE_CURRENT_SOURCE_DIR}}/{i}"
i = f"$<BUILD_INTERFACE:{i}>" i = f"$<BUILD_INTERFACE:{i}>"
processed_includes.append(i) 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") 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) 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: 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: if len(client_sources) == 0 and len(server_sources) == 0:
return return
condition = "ON" condition, indent = write_scope_condition_begin(cm_fh, scope, indent=indent)
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
if len(client_sources) != 0: if len(client_sources) != 0:
cm_fh.write(f"\n{spaces(indent)}qt6_generate_wayland_protocol_client_sources({target}\n") 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") 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": if condition != "ON":
indent -= 1 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]): 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_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"): if "warn_off" in scope.get("CONFIG"):
write_generic_cmake_command(cm_fh, "qt_disable_warnings", [name]) write_generic_cmake_command(cm_fh, "qt_disable_warnings", [name])
@ -2965,6 +3152,9 @@ def write_main_part(
c.reset_visited_keys() c.reset_visited_keys()
write_android_part(cm_fh, name, c, indent=indent) write_android_part(cm_fh, name, c, indent=indent)
write_wayland_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_extend_target(cm_fh, name, c, target_ref=target_ref, indent=indent)
write_simd_part(cm_fh, name, c, 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 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") s = scope.get_string("QMAKE_TARGET_PRODUCT")
if s: if s:
extra.append(f"TARGET_PRODUCT \"{s}\"") extra.append(f"TARGET_PRODUCT \"{s}\"")
s = scope.get_string("QMAKE_TARGET_DESCRIPTION") 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}\"") extra.append(f"TARGET_DESCRIPTION \"{s}\"")
s = scope.get_string("QMAKE_TARGET_COMPANY") s = scope.get_string("QMAKE_TARGET_COMPANY")
if s: if s:
@ -3079,6 +3270,7 @@ def forward_target_info(scope: Scope, extra: [str]):
if s: if s:
extra.append(f"TARGET_COPYRIGHT \"{s}\"") extra.append(f"TARGET_COPYRIGHT \"{s}\"")
def write_module(cm_fh: IO[str], scope: Scope, *, indent: int = 0) -> str: def write_module(cm_fh: IO[str], scope: Scope, *, indent: int = 0) -> str:
module_name = scope.TARGET module_name = scope.TARGET
if not module_name.startswith("Qt"): 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}" 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: def write_test(cm_fh: IO[str], scope: Scope, gui: bool = False, *, indent: int = 0) -> str:
test_name = scope.TARGET test_name = scope.TARGET
assert test_name assert test_name
@ -3809,6 +4025,10 @@ def handle_app_or_lib(
elif "qt_tool" in scope.get("_LOADED"): elif "qt_tool" in scope.get("_LOADED"):
assert not is_example assert not is_example
target, target_ref = write_tool(cm_fh, scope, indent=indent) 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: else:
if "testcase" in config or "testlib" in config or "qmltestcase" in config: if "testcase" in config or "testlib" in config or "qmltestcase" in config:
assert not is_example assert not is_example