From 214c3a033a4e9191f0082bf6f88624d356a45384 Mon Sep 17 00:00:00 2001 From: Alexey Edelev Date: Wed, 18 Jan 2023 22:06:08 +0100 Subject: [PATCH] Add simple project generation based on existing source files Introduce the qt-cmake-create script. The script generates the simple CMakeLists.txt based on the source files located in the current or specified directory. The initial version can generate a CMake code for the following file types: - .c .cc .cpp .cxx .h .hh .hxx .hpp - generates the qt_add_executable call with prerequisites. - .qml .js .mjs - generates the qt_add_qml_module call with prerequisites. - .ui - adds the found ui files to the existing executable. Requires C++ files be present in the directory too. - .qrc - generates the qt_add_resources call and adds the resources to the existing executable. Requires C++ files be present in the directory too. - .proto - generates qt_add_protobuf call with prerequisites. The QtInitProject.cmake script contains the 'handle_type' function that allows extending the script capabilities and establish simple relation chains between the file types. Note: The initial implementation doesn't deal with sub-directories, so all files from sub-directories will be added to and handled in the top-level CMakeLists.txt file. This can be extended by user request. Task-number: QTBUG-104388 Change-Id: I5abd9e07da109e867ff95986572ed2bf02ef9d3d Reviewed-by: Qt CI Bot Reviewed-by: Alexandru Croitor --- bin/qt-cmake-create.bat.in | 18 ++ bin/qt-cmake-create.in | 29 +++ cmake/QtBaseGlobalTargets.cmake | 1 + cmake/QtInitProject.cmake | 214 ++++++++++++++++++ cmake/QtWrapperScriptHelpers.cmake | 14 ++ cmake/README.md | 13 ++ tests/auto/tools/CMakeLists.txt | 1 + .../auto/tools/qt_cmake_create/CMakeLists.txt | 14 ++ .../cpp_project/CMakeLists.txt.expected | 14 ++ .../testdata/cpp_project/main.cpp | 7 + .../proto_project/CMakeLists.txt.expected | 17 ++ .../testdata/proto_project/test.proto | 5 + .../qml_project/CMakeLists.txt.expected | 27 +++ .../testdata/qml_project/TestComponent.qml | 4 + .../testdata/qml_project/main.cpp | 18 ++ .../testdata/qml_project/main.qml | 16 ++ .../qrc_project/CMakeLists.txt.expected | 20 ++ .../testdata/qrc_project/main.cpp | 13 ++ .../testdata/qrc_project/test.qrc | 5 + .../testdata/qrc_project/test.txt | 1 + .../testdata/ui_only_project/widget.ui | 32 +++ .../ui_project/CMakeLists.txt.expected | 23 ++ .../testdata/ui_project/main.cpp | 11 + .../testdata/ui_project/widget.cpp | 15 ++ .../testdata/ui_project/widget.h | 21 ++ .../testdata/ui_project/widget.ui | 32 +++ .../qt_cmake_create/tst_qt_cmake_create.cpp | 157 +++++++++++++ 27 files changed, 742 insertions(+) create mode 100644 bin/qt-cmake-create.bat.in create mode 100755 bin/qt-cmake-create.in create mode 100644 cmake/QtInitProject.cmake create mode 100644 tests/auto/tools/qt_cmake_create/CMakeLists.txt create mode 100644 tests/auto/tools/qt_cmake_create/testdata/cpp_project/CMakeLists.txt.expected create mode 100644 tests/auto/tools/qt_cmake_create/testdata/cpp_project/main.cpp create mode 100644 tests/auto/tools/qt_cmake_create/testdata/proto_project/CMakeLists.txt.expected create mode 100644 tests/auto/tools/qt_cmake_create/testdata/proto_project/test.proto create mode 100644 tests/auto/tools/qt_cmake_create/testdata/qml_project/CMakeLists.txt.expected create mode 100644 tests/auto/tools/qt_cmake_create/testdata/qml_project/TestComponent.qml create mode 100644 tests/auto/tools/qt_cmake_create/testdata/qml_project/main.cpp create mode 100644 tests/auto/tools/qt_cmake_create/testdata/qml_project/main.qml create mode 100644 tests/auto/tools/qt_cmake_create/testdata/qrc_project/CMakeLists.txt.expected create mode 100644 tests/auto/tools/qt_cmake_create/testdata/qrc_project/main.cpp create mode 100644 tests/auto/tools/qt_cmake_create/testdata/qrc_project/test.qrc create mode 100644 tests/auto/tools/qt_cmake_create/testdata/qrc_project/test.txt create mode 100644 tests/auto/tools/qt_cmake_create/testdata/ui_only_project/widget.ui create mode 100644 tests/auto/tools/qt_cmake_create/testdata/ui_project/CMakeLists.txt.expected create mode 100644 tests/auto/tools/qt_cmake_create/testdata/ui_project/main.cpp create mode 100644 tests/auto/tools/qt_cmake_create/testdata/ui_project/widget.cpp create mode 100644 tests/auto/tools/qt_cmake_create/testdata/ui_project/widget.h create mode 100644 tests/auto/tools/qt_cmake_create/testdata/ui_project/widget.ui create mode 100644 tests/auto/tools/qt_cmake_create/tst_qt_cmake_create.cpp diff --git a/bin/qt-cmake-create.bat.in b/bin/qt-cmake-create.bat.in new file mode 100644 index 0000000000..ff8f7310e9 --- /dev/null +++ b/bin/qt-cmake-create.bat.in @@ -0,0 +1,18 @@ +@echo off +:: The directory of this script is the expanded absolute path of the "$qt_prefix/bin" directory. +set script_dir_path=%~dp0 + +:: Try to use original cmake, otherwise to make it relocatable, use any cmake found in PATH. +set cmake_path=@CMAKE_COMMAND@ +if not exist "%cmake_path%" set cmake_path=cmake + +if NOT "%~2" == "" goto :showhelp +if NOT "%~1" == "" (set PROJECT_DIR=%~1) else (set PROJECT_DIR=%cd%) + +"%cmake_path%" -DPROJECT_DIR="%PROJECT_DIR%" -P "%script_dir_path%\@__GlobalConfig_relative_path_from_bin_dir_to_cmake_config_dir@\QtInitProject.cmake" +exit /b %errorlevel% + +:showhelp +echo Usage +echo. qt-cmake-create +exit /b 1 diff --git a/bin/qt-cmake-create.in b/bin/qt-cmake-create.in new file mode 100755 index 0000000000..7865d0fe91 --- /dev/null +++ b/bin/qt-cmake-create.in @@ -0,0 +1,29 @@ +#!/bin/sh + +HELP_MESSAGE="Usage + qt-cmake-create " + +# The directory of this script is the expanded absolute path of the "$qt_prefix/bin" directory. +script_dir_path=`dirname $0` +script_dir_path=`(cd "$script_dir_path"; /bin/pwd)` + +# Try to use original cmake, otherwise to make it relocatable, use any cmake found in PATH. +original_cmake_path="@CMAKE_COMMAND@" +cmake_path=$original_cmake_path +if ! test -f "$cmake_path"; then + cmake_path="cmake" +fi + +if [ "$#" -gt 1 ]; then + echo "Invalid number of arguments" + echo "$HELP_MESSAGE" + exit 1 +fi + +if [ "$#" -gt 0 ]; then + PROJECT_DIR=$1 +else + PROJECT_DIR=$PWD +fi +exec "$cmake_path" -DPROJECT_DIR="$PROJECT_DIR" -P \ + "$script_dir_path/@__GlobalConfig_relative_path_from_bin_dir_to_cmake_config_dir@/QtInitProject.cmake" diff --git a/cmake/QtBaseGlobalTargets.cmake b/cmake/QtBaseGlobalTargets.cmake index cc551ed08f..b3eea3006f 100644 --- a/cmake/QtBaseGlobalTargets.cmake +++ b/cmake/QtBaseGlobalTargets.cmake @@ -330,6 +330,7 @@ set(__public_cmake_helpers cmake/QtCopyFileIfDifferent.cmake cmake/QtFeature.cmake cmake/QtFeatureCommon.cmake + cmake/QtInitProject.cmake cmake/QtPublicAppleHelpers.cmake cmake/QtPublicCMakeHelpers.cmake cmake/QtPublicCMakeVersionHelpers.cmake diff --git a/cmake/QtInitProject.cmake b/cmake/QtInitProject.cmake new file mode 100644 index 0000000000..3a8d11459f --- /dev/null +++ b/cmake/QtInitProject.cmake @@ -0,0 +1,214 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause +cmake_minimum_required(VERSION 3.16) + +if(NOT PROJECT_DIR) + set(PROJECT_DIR "${CMAKE_SOURCE_DIR}") +endif() + +get_filename_component(project_name "${PROJECT_DIR}" NAME) + +get_filename_component(project_abs_dir "${PROJECT_DIR}" ABSOLUTE) +if(NOT IS_DIRECTORY "${project_abs_dir}") + message(FATAL_ERROR "Unable to scan ${project_abs_dir}. The directory doesn't exist.") +endif() + +set(known_extensions "") +set(types "") + +# The function allows extending the capabilities of this script and establishes simple relation +# chains between the file types. +# Option Arguments: +# EXPERIMENTAL +# Marks that the support of the following files is experimental and the required Qt modules +# are in Technical preview state. +# DEPRECATED +# Marks that the support of the following files will be discontinued soon and the required +# Qt modules are deprecated. +# One-value Arguments: +# TEMPLATE +# The CMake code template. Use the '@files@' string for the files substitution. +# Multi-value Arguments: +# EXTENSIONS +# List of the file extensions treated as this source 'type'. +# MODULES +# List of Qt modules required for these file types. +# DEPENDS +# The prerequisite source 'type' needed by this source 'type' +macro(handle_type type) + cmake_parse_arguments(arg + "EXPERIMENTAL;DEPRECATED" + "TEMPLATE" + "EXTENSIONS;MODULES;DEPENDS" + ${ARGN} + ) + + if(NOT arg_EXTENSIONS) + message(FATAL_ERROR "Unexpected call handle_type of with no EXTENSIONS specified." + " This is the Qt issue, please report a bug at https://bugreports.qt.io.") + endif() + set(unique_extensions_subset "${known_extensions}") + list(REMOVE_ITEM unique_extensions_subset ${arg_EXTENSIONS}) + if(NOT "${known_extensions}" STREQUAL "${unique_extensions_subset}") + message(FATAL_ERROR "${type} contains duplicated extensions, this is not supported." + " This is the Qt issue, please report a bug at https://bugreports.qt.io.") + endif() + set(${type}_file_extensions "${arg_EXTENSIONS}") + + if(NOT arg_TEMPLATE) + message(FATAL_ERROR "Unexpected call handle_type of with no TEMPLATE specified." + " This is the Qt issue, please report a bug at https://bugreports.qt.io.") + endif() + set(${type}_template "${arg_TEMPLATE}") + + if(arg_MODULES) + set(${type}_required_modules "${arg_MODULES}") + endif() + + list(APPEND types ${type}) + + if(arg_EXPERIMENTAL) + set(${type}_is_experimental TRUE) + else() + set(${type}_is_experimental FALSE) + endif() + + if(arg_DEPRECATED) + set(${type}_is_deprecated TRUE) + else() + set(${type}_is_deprecated FALSE) + endif() + + if(arg_DEPENDS) + set(${type}_dependencies ${arg_DEPENDS}) + endif() +endmacro() + +handle_type(cpp EXTENSIONS .c .cc .cpp .cxx .h .hh .hxx .hpp MODULES Core TEMPLATE +"\n\nqt_add_executable(${project_name} + @files@ +)" +) + +handle_type(qml EXTENSIONS .qml .js .mjs MODULES Gui Qml Quick TEMPLATE +"\n\nqt_add_qml_module(${project_name} + URI ${project_name} + OUTPUT_DIRECTORY qml + VERSION 1.0 + AUTO_RESOURCE_PREFIX + QML_FILES + @files@ +)" +) + +handle_type(ui EXTENSIONS .ui MODULES Gui Widgets DEPENDS cpp TEMPLATE +"\n\ntarget_sources(${project_name} + PRIVATE + @files@ +)" +) + +handle_type(qrc EXTENSIONS .qrc DEPENDS cpp TEMPLATE +"\n\nqt_add_resources(${project_name}_resources @files@) +target_sources(${project_name} + PRIVATE + \\\${${project_name}_resources} +)" +) + +handle_type(protobuf EXPERIMENTAL EXTENSIONS .proto MODULES Protobuf Grpc TEMPLATE +"\n\nqt_add_protobuf(${project_name} + GENERATE_PACKAGE_SUBFOLDERS + PROTO_FILES + @files@ +)" +) + +set(extra_packages "") +file(GLOB_RECURSE files RELATIVE "${project_abs_dir}" "${project_abs_dir}/*") +foreach(f IN LISTS files) + get_filename_component(file_extension "${f}" LAST_EXT) + string(TOLOWER "${file_extension}" file_extension) + + foreach(type IN LISTS types) + if(file_extension IN_LIST ${type}_file_extensions) + list(APPEND ${type}_sources "${f}") + list(APPEND packages ${${type}_required_modules}) + if(${type}_is_experimental) + message("We found files with the following extensions in your directory:" + " ${${type}_file_extensions}\n" + "Note that the modules ${${type}_required_modules} are" + " in the technical preview state.") + endif() + if(${type}_is_deprecated) + message("We found files with the following extensions in your directory:" + " ${${type}_file_extensions}\n" + "Note that the modules ${${type}_required_modules} are deprecated.") + endif() + break() + endif() + endforeach() +endforeach() + +if(packages) + list(REMOVE_DUPLICATES packages) + list(JOIN packages " " packages_string) + list(JOIN packages "\n Qt::" deps_string) + set(deps_string "Qt::${deps_string}") +endif() + +set(content +"cmake_minimum_required(VERSION 3.16) +project(${project_name} LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS ${packages_string}) +qt_standard_project_setup()" +) + +set(has_useful_sources FALSE) +foreach(type IN LISTS types) + if(${type}_sources) + set(skip FALSE) + foreach(dep IN LISTS ${type}_dependencies) + if(NOT ${dep}_sources) + set(skip TRUE) + message("Sources of type ${${type}_file_extensions} cannot live in the project" + " without ${${dep}_file_extensions} files. Skipping.") + break() + endif() + endforeach() + if(skip) + continue() + endif() + + set(has_useful_sources TRUE) + string(REGEX MATCH "( +)@files@" unused "${${type}_template}") + list(JOIN ${type}_sources "\n${CMAKE_MATCH_1}" ${type}_sources) + string(REPLACE "@files@" "${${type}_sources}" ${type}_content + "${${type}_template}") + string(APPEND content "${${type}_content}") + endif() +endforeach() + +string(APPEND content "\n\ntarget_link_libraries(${project_name} + PRIVATE + ${deps_string} +)\n" +) + +if(EXISTS "${project_abs_dir}/CMakeLists.txt") + message(FATAL_ERROR "Project is already initialized in current directory." + " Please remove CMakeLists.txt if you want to regenerate the project.") +endif() + +if(NOT has_useful_sources) + message(FATAL_ERROR "Could not find any files to generate the project.") +endif() +file(WRITE "${project_abs_dir}/CMakeLists.txt" "${content}") + +message("The project file is successfully generated. To build the project run:" + "\nmkdir build" + "\ncd build" + "\nqt-cmake ${project_abs_dir}" + "\ncmake --build ${project_abs_dir}" +) diff --git a/cmake/QtWrapperScriptHelpers.cmake b/cmake/QtWrapperScriptHelpers.cmake index bfb6d0d7e6..e87d0ad2e5 100644 --- a/cmake/QtWrapperScriptHelpers.cmake +++ b/cmake/QtWrapperScriptHelpers.cmake @@ -39,6 +39,20 @@ function(qt_internal_create_wrapper_scripts) DESTINATION "${INSTALL_BINDIR}") endif() + if(generate_unix) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/bin/qt-cmake-create.in" + "${QT_BUILD_DIR}/${INSTALL_BINDIR}/qt-cmake-create" @ONLY + NEWLINE_STYLE LF) + qt_install(PROGRAMS "${QT_BUILD_DIR}/${INSTALL_BINDIR}/qt-cmake-create" + DESTINATION "${INSTALL_BINDIR}") + endif() + if(generate_non_unix) + configure_file("${CMAKE_CURRENT_SOURCE_DIR}/bin/qt-cmake-create.bat.in" + "${QT_BUILD_DIR}/${INSTALL_BINDIR}/qt-cmake-create.bat" @ONLY + NEWLINE_STYLE CRLF) + qt_install(PROGRAMS "${QT_BUILD_DIR}/${INSTALL_BINDIR}/qt-cmake-create.bat" + DESTINATION "${INSTALL_BINDIR}") + endif() # Provide a private convenience wrapper with options which should not be propagated via the # public qt-cmake wrapper e.g. CMAKE_GENERATOR. # These options can not be set in a toolchain file, but only on the command line. diff --git a/cmake/README.md b/cmake/README.md index 9800dae1ce..ac9d1012cf 100644 --- a/cmake/README.md +++ b/cmake/README.md @@ -313,3 +313,16 @@ $ cd some/empty/directory $ ~/Qt/6.0.0/bin/qt-cmake-standalone-test ~/source/of/qtbase/test/auto/corelib/io/qprocess $ cmake --build . ``` + +## qt-cmake-create + +Generates a simple CMakeLists.txt based on source files in specified project directory. + +Example: + +``` +$ cd some/source/directory/ +$ qt-cmake-create +$ qt-cmake -S . -B /build/directory +$ cmake --build /build/directory +``` diff --git a/tests/auto/tools/CMakeLists.txt b/tests/auto/tools/CMakeLists.txt index 9e59771aa0..88c9b85a53 100644 --- a/tests/auto/tools/CMakeLists.txt +++ b/tests/auto/tools/CMakeLists.txt @@ -11,6 +11,7 @@ if(NOT ANDROID AND NOT IOS) endif() add_subdirectory(moc) add_subdirectory(rcc) + add_subdirectory(qt_cmake_create) endif() # QTBUG-88538 # special case if(TARGET Qt::Widgets AND NOT ANDROID AND NOT IOS) diff --git a/tests/auto/tools/qt_cmake_create/CMakeLists.txt b/tests/auto/tools/qt_cmake_create/CMakeLists.txt new file mode 100644 index 0000000000..6611471388 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +# Collect test data +file(GLOB_RECURSE test_data_glob + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + testdata/*) +list(APPEND test_data ${test_data_glob}) + +qt_internal_add_test(tst_qt_cmake_create + SOURCES + tst_qt_cmake_create.cpp + TESTDATA ${test_data} +) diff --git a/tests/auto/tools/qt_cmake_create/testdata/cpp_project/CMakeLists.txt.expected b/tests/auto/tools/qt_cmake_create/testdata/cpp_project/CMakeLists.txt.expected new file mode 100644 index 0000000000..7e646b722d --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/cpp_project/CMakeLists.txt.expected @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.16) +project(cpp_project LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Core) +qt_standard_project_setup() + +qt_add_executable(cpp_project + main.cpp +) + +target_link_libraries(cpp_project + PRIVATE + Qt::Core +) diff --git a/tests/auto/tools/qt_cmake_create/testdata/cpp_project/main.cpp b/tests/auto/tools/qt_cmake_create/testdata/cpp_project/main.cpp new file mode 100644 index 0000000000..ae659d5ed4 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/cpp_project/main.cpp @@ -0,0 +1,7 @@ +#include + +int main(int, char *[]) +{ + std::cout << "Now I have CMakeLists.txt. Thanks!" << std::endl; + return 0; +} diff --git a/tests/auto/tools/qt_cmake_create/testdata/proto_project/CMakeLists.txt.expected b/tests/auto/tools/qt_cmake_create/testdata/proto_project/CMakeLists.txt.expected new file mode 100644 index 0000000000..1b9ae912b2 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/proto_project/CMakeLists.txt.expected @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.16) +project(proto_project LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Protobuf Grpc) +qt_standard_project_setup() + +qt_add_protobuf(proto_project + GENERATE_PACKAGE_SUBFOLDERS + PROTO_FILES + test.proto +) + +target_link_libraries(proto_project + PRIVATE + Qt::Protobuf + Qt::Grpc +) diff --git a/tests/auto/tools/qt_cmake_create/testdata/proto_project/test.proto b/tests/auto/tools/qt_cmake_create/testdata/proto_project/test.proto new file mode 100644 index 0000000000..e03a5c0832 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/proto_project/test.proto @@ -0,0 +1,5 @@ +syntax = "proto3"; + +package test; +message Noop { +} diff --git a/tests/auto/tools/qt_cmake_create/testdata/qml_project/CMakeLists.txt.expected b/tests/auto/tools/qt_cmake_create/testdata/qml_project/CMakeLists.txt.expected new file mode 100644 index 0000000000..1befe64ffd --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/qml_project/CMakeLists.txt.expected @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.16) +project(qml_project LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Gui Qml Quick Core) +qt_standard_project_setup() + +qt_add_executable(qml_project + main.cpp +) + +qt_add_qml_module(qml_project + URI qml_project + OUTPUT_DIRECTORY qml + VERSION 1.0 + AUTO_RESOURCE_PREFIX + QML_FILES + TestComponent.qml + main.qml +) + +target_link_libraries(qml_project + PRIVATE + Qt::Gui + Qt::Qml + Qt::Quick + Qt::Core +) diff --git a/tests/auto/tools/qt_cmake_create/testdata/qml_project/TestComponent.qml b/tests/auto/tools/qt_cmake_create/testdata/qml_project/TestComponent.qml new file mode 100644 index 0000000000..f97cbcf115 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/qml_project/TestComponent.qml @@ -0,0 +1,4 @@ +import QtQuick + +Item { +} diff --git a/tests/auto/tools/qt_cmake_create/testdata/qml_project/main.cpp b/tests/auto/tools/qt_cmake_create/testdata/qml_project/main.cpp new file mode 100644 index 0000000000..13c7014d8c --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/qml_project/main.cpp @@ -0,0 +1,18 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + const QUrl url(QStringLiteral("qrc:/qt/qml/qml_project/main.qml")); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, + &app, [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) + QCoreApplication::exit(-1); + }, Qt::QueuedConnection); + engine.load(url); + + return app.exec(); +} diff --git a/tests/auto/tools/qt_cmake_create/testdata/qml_project/main.qml b/tests/auto/tools/qt_cmake_create/testdata/qml_project/main.qml new file mode 100644 index 0000000000..b42c9e7897 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/qml_project/main.qml @@ -0,0 +1,16 @@ +import QtQuick +import QtQuick.Window + +Window { + width: 640 + height: 480 + visible: true + title: "Hello World" + Text { + anchors.centerIn: parent + font.pointSize: 16 + text: "Now I have CMakeLists.txt. Thanks!" + } + TestComponent { + } +} diff --git a/tests/auto/tools/qt_cmake_create/testdata/qrc_project/CMakeLists.txt.expected b/tests/auto/tools/qt_cmake_create/testdata/qrc_project/CMakeLists.txt.expected new file mode 100644 index 0000000000..8a595ac3c2 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/qrc_project/CMakeLists.txt.expected @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.16) +project(qrc_project LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Core) +qt_standard_project_setup() + +qt_add_executable(qrc_project + main.cpp +) + +qt_add_resources(qrc_project_resources test.qrc) +target_sources(qrc_project + PRIVATE + ${qrc_project_resources} +) + +target_link_libraries(qrc_project + PRIVATE + Qt::Core +) diff --git a/tests/auto/tools/qt_cmake_create/testdata/qrc_project/main.cpp b/tests/auto/tools/qt_cmake_create/testdata/qrc_project/main.cpp new file mode 100644 index 0000000000..cd8ed8f57b --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/qrc_project/main.cpp @@ -0,0 +1,13 @@ +#include +#include + +int main(int, char **) +{ + QFile file(":/test.txt"); + if (!file.open(QFile::ReadOnly)) + return 1; + + QString data = QString::fromUtf8(file.readAll()); + qDebug() << data; + return 0; +} diff --git a/tests/auto/tools/qt_cmake_create/testdata/qrc_project/test.qrc b/tests/auto/tools/qt_cmake_create/testdata/qrc_project/test.qrc new file mode 100644 index 0000000000..adfe37b52e --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/qrc_project/test.qrc @@ -0,0 +1,5 @@ + + + test.txt + + diff --git a/tests/auto/tools/qt_cmake_create/testdata/qrc_project/test.txt b/tests/auto/tools/qt_cmake_create/testdata/qrc_project/test.txt new file mode 100644 index 0000000000..6c5b215ebe --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/qrc_project/test.txt @@ -0,0 +1 @@ +Now I have CMakeLists.txt. Thanks! diff --git a/tests/auto/tools/qt_cmake_create/testdata/ui_only_project/widget.ui b/tests/auto/tools/qt_cmake_create/testdata/ui_only_project/widget.ui new file mode 100644 index 0000000000..d2c4f620b1 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/ui_only_project/widget.ui @@ -0,0 +1,32 @@ + + + Widget + + + + 0 + 0 + 800 + 600 + + + + Widget + + + + + 10 + 6 + 781 + 581 + + + + Now I have CMakeLists.txt. Thanks! + + + + + + diff --git a/tests/auto/tools/qt_cmake_create/testdata/ui_project/CMakeLists.txt.expected b/tests/auto/tools/qt_cmake_create/testdata/ui_project/CMakeLists.txt.expected new file mode 100644 index 0000000000..6252b71389 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/ui_project/CMakeLists.txt.expected @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.16) +project(ui_project LANGUAGES CXX) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) +qt_standard_project_setup() + +qt_add_executable(ui_project + main.cpp + widget.cpp + widget.h +) + +target_sources(ui_project + PRIVATE + widget.ui +) + +target_link_libraries(ui_project + PRIVATE + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/tests/auto/tools/qt_cmake_create/testdata/ui_project/main.cpp b/tests/auto/tools/qt_cmake_create/testdata/ui_project/main.cpp new file mode 100644 index 0000000000..b0a4ec2647 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/ui_project/main.cpp @@ -0,0 +1,11 @@ +#include "widget.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + Widget w; + w.show(); + return a.exec(); +} diff --git a/tests/auto/tools/qt_cmake_create/testdata/ui_project/widget.cpp b/tests/auto/tools/qt_cmake_create/testdata/ui_project/widget.cpp new file mode 100644 index 0000000000..815d5f8c4b --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/ui_project/widget.cpp @@ -0,0 +1,15 @@ +#include "widget.h" +#include "ui_widget.h" + +Widget::Widget(QWidget *parent) + : QWidget(parent) + , ui(new Ui::Widget) +{ + ui->setupUi(this); +} + +Widget::~Widget() +{ + delete ui; +} + diff --git a/tests/auto/tools/qt_cmake_create/testdata/ui_project/widget.h b/tests/auto/tools/qt_cmake_create/testdata/ui_project/widget.h new file mode 100644 index 0000000000..1fe2322b13 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/ui_project/widget.h @@ -0,0 +1,21 @@ +#ifndef WIDGET_H +#define WIDGET_H + +#include + +QT_BEGIN_NAMESPACE +namespace Ui { class Widget; } +QT_END_NAMESPACE + +class Widget : public QWidget +{ + Q_OBJECT + +public: + Widget(QWidget *parent = nullptr); + ~Widget(); + +private: + Ui::Widget *ui; +}; +#endif // WIDGET_H diff --git a/tests/auto/tools/qt_cmake_create/testdata/ui_project/widget.ui b/tests/auto/tools/qt_cmake_create/testdata/ui_project/widget.ui new file mode 100644 index 0000000000..d2c4f620b1 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/testdata/ui_project/widget.ui @@ -0,0 +1,32 @@ + + + Widget + + + + 0 + 0 + 800 + 600 + + + + Widget + + + + + 10 + 6 + 781 + 581 + + + + Now I have CMakeLists.txt. Thanks! + + + + + + diff --git a/tests/auto/tools/qt_cmake_create/tst_qt_cmake_create.cpp b/tests/auto/tools/qt_cmake_create/tst_qt_cmake_create.cpp new file mode 100644 index 0000000000..5d358ec6a2 --- /dev/null +++ b/tests/auto/tools/qt_cmake_create/tst_qt_cmake_create.cpp @@ -0,0 +1,157 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace Qt::Literals::StringLiterals; + +class tst_qt_cmake_create : public QObject +{ + Q_OBJECT + +public: + tst_qt_cmake_create(); + +private slots: + void init(); + void initTestCase(); + void generatingCMakeLists_data(); + void generatingCMakeLists(); + +private: + QString m_testWorkDir; + QString m_shell; + QString m_cmd; +}; + +tst_qt_cmake_create::tst_qt_cmake_create() : m_testWorkDir(qApp->applicationDirPath()) { } + +void tst_qt_cmake_create::initTestCase() +{ + QString binpath = QLibraryInfo::path(QLibraryInfo::BinariesPath); +#ifdef Q_OS_WINDOWS + m_shell = QString("cmd.exe"); + m_cmd = QString("%1/qt-cmake-create.bat").arg(binpath); +#else + m_shell = QString("/bin/sh"); + m_cmd = QString("%1/qt-cmake-create").arg(binpath); + QVERIFY(QFile::exists(m_shell)); +#endif + + QVERIFY(QFile::exists(m_cmd)); +} + +void tst_qt_cmake_create::init() +{ + QFETCH(QString, projectDirPath); + QDir workDir(m_testWorkDir); + QString fullProjectPath = m_testWorkDir + '/' + projectDirPath; + if (workDir.exists(fullProjectPath)) { + QDir projectDir(projectDirPath); + projectDir.removeRecursively(); + } + workDir.mkdir(projectDirPath); + auto testDataPath = QFINDTESTDATA("testdata"_L1 + '/' + projectDirPath); + QVERIFY(QFile::exists(testDataPath)); + + for (const auto &fileInfo : QDir(testDataPath).entryInfoList(QDir::Files)) { + QVERIFY(QFile::copy(fileInfo.absoluteFilePath(), + fullProjectPath + '/' + fileInfo.fileName())); + } +} + +void tst_qt_cmake_create::generatingCMakeLists_data() +{ + QTest::addColumn("projectDirPath"); + QTest::addColumn("expectPass"); + QTest::addColumn("workDir"); + + const std::array expectPass = { + "cpp"_L1, "proto"_L1, "qml"_L1, "qrc"_L1, "ui"_L1, + }; + + const std::array workDirs = { + m_testWorkDir, ""_L1, m_testWorkDir, ""_L1, m_testWorkDir, + }; + + static_assert(expectPass.size() == workDirs.size()); + + const QLatin1StringView expectFail[] = { + "ui_only"_L1, + }; + + for (size_t i = 0; i < expectPass.size(); ++i) { + const auto type = expectPass.at(i); + QTest::addRow("tst_qt_cmake_create_%s", type.data()) + << QString("%1_project").arg(type) << true << workDirs.at(i); + } + + for (const auto type : expectFail) { + QTest::addRow("tst_qt_cmake_create_%s", type.data()) + << QString("%1_project").arg(type) << false << QString(); + } +} + +void tst_qt_cmake_create::generatingCMakeLists() +{ + QFETCH(QString, projectDirPath); + QFETCH(bool, expectPass); + QFETCH(QString, workDir); + + QString fullProjectPath = m_testWorkDir + '/' + projectDirPath; + QProcess command; + QStringList arguments = { +#ifdef Q_OS_WINDOWS + "/C"_L1, +#endif + m_cmd + }; + + QString workingDirectory = fullProjectPath; + if (!workDir.isEmpty()) { + workingDirectory = workDir; + arguments.append(fullProjectPath); + } + command.setProgram(m_shell); + command.setArguments(arguments); + command.setWorkingDirectory(workingDirectory); + + command.start(); + QVERIFY(command.waitForFinished()); + QCOMPARE(command.exitCode() == 0, expectPass); + + QFile actualFile = QFile(fullProjectPath + '/' + "CMakeLists.txt"_L1); + + // Skip the rest if we expect that qt-cmake-create should exit with error + if (!expectPass) { + QVERIFY(!actualFile.exists()); + return; + } + + QFile expectedFile = QFile(fullProjectPath + '/' + "CMakeLists.txt.expected"_L1); + QVERIFY(actualFile.open(QFile::ReadOnly)); + QVERIFY(expectedFile.open(QFile::ReadOnly)); + + auto actualData = actualFile.readAll(); + actualData.replace(QByteArrayView("\r\n"), QByteArrayView("\n")); + auto expectedData = expectedFile.readAll(); + expectedData.replace(QByteArrayView("\r\n"), QByteArrayView("\n")); + + static auto hash = [](const QByteArray &data) { + return QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex(); + }; + QCOMPARE_EQ(hash(actualData), hash(expectedData)); +} + +QTEST_MAIN(tst_qt_cmake_create) +#include "tst_qt_cmake_create.moc"