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:
parent
b3b1e47378
commit
d65283ebd0
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user