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 <qt_ci_bot@qt-project.org>
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
This commit is contained in:
Alexey Edelev 2023-01-18 22:06:08 +01:00
parent 52150469a8
commit 214c3a033a
27 changed files with 742 additions and 0 deletions

View File

@ -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 <path/to/project>
exit /b 1

29
bin/qt-cmake-create.in Executable file
View File

@ -0,0 +1,29 @@
#!/bin/sh
HELP_MESSAGE="Usage
qt-cmake-create <path/to/project>"
# 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"

View File

@ -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

214
cmake/QtInitProject.cmake Normal file
View File

@ -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}"
)

View File

@ -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.

View File

@ -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
```

View File

@ -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)

View File

@ -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}
)

View File

@ -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
)

View File

@ -0,0 +1,7 @@
#include <iostream>
int main(int, char *[])
{
std::cout << "Now I have CMakeLists.txt. Thanks!" << std::endl;
return 0;
}

View File

@ -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
)

View File

@ -0,0 +1,5 @@
syntax = "proto3";
package test;
message Noop {
}

View File

@ -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
)

View File

@ -0,0 +1,4 @@
import QtQuick
Item {
}

View File

@ -0,0 +1,18 @@
#include <QGuiApplication>
#include <QQmlApplicationEngine>
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();
}

View File

@ -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 {
}
}

View File

@ -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
)

View File

@ -0,0 +1,13 @@
#include <QFile>
#include <QDebug>
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;
}

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>test.txt</file>
</qresource>
</RCC>

View File

@ -0,0 +1 @@
Now I have CMakeLists.txt. Thanks!

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>10</x>
<y>6</y>
<width>781</width>
<height>581</height>
</rect>
</property>
<property name="text">
<string>Now I have CMakeLists.txt. Thanks!</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -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
)

View File

@ -0,0 +1,11 @@
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

View File

@ -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;
}

View File

@ -0,0 +1,21 @@
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
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

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Widget</string>
</property>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>10</x>
<y>6</y>
<width>781</width>
<height>581</height>
</rect>
</property>
<property name="text">
<string>Now I have CMakeLists.txt. Thanks!</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -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 <QTest>
#include <QtTest>
#include <QLibraryInfo>
#include <QLatin1StringView>
#include <QDir>
#include <QFileInfo>
#include <QProcess>
#include <QCryptographicHash>
#include <array>
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<QString>("projectDirPath");
QTest::addColumn<bool>("expectPass");
QTest::addColumn<QString>("workDir");
const std::array<QLatin1StringView, 5> expectPass = {
"cpp"_L1, "proto"_L1, "qml"_L1, "qrc"_L1, "ui"_L1,
};
const std::array<QString, 5> 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"