+
+using namespace Utils;
+
+namespace Vcpkg::Internal::Search {
+
+class VcpkgPackageSearchDialog : public QDialog
+{
+public:
+ explicit VcpkgPackageSearchDialog(QWidget *parent);
+
+ VcpkgManifest selectedPackage() const;
+
+private:
+ void listPackages(const QString &filter);
+ void showPackageDetails(const QString &packageName);
+
+ VcpkgManifests m_allPackages;
+ VcpkgManifest m_selectedPackage;
+
+ FancyLineEdit *m_packagesFilter;
+ ListWidget *m_packagesList;
+ QLineEdit *m_vcpkgName;
+ QLabel *m_vcpkgVersion;
+ QLabel *m_vcpkgLicense;
+ QTextBrowser *m_vcpkgDescription;
+ QLabel *m_vcpkgHomepage;
+ QDialogButtonBox *m_buttonBox;
+};
+
+VcpkgPackageSearchDialog::VcpkgPackageSearchDialog(QWidget *parent)
+ : QDialog(parent)
+{
+ resize(920, 400);
+
+ m_packagesFilter = new FancyLineEdit;
+ m_packagesFilter->setFiltering(true);
+ m_packagesFilter->setFocus();
+ m_packagesFilter->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
+
+ m_packagesList = new ListWidget;
+ m_packagesList->setMaximumWidth(300);
+
+ m_vcpkgName = new QLineEdit;
+ m_vcpkgName->setReadOnly(true);
+
+ m_vcpkgVersion = new QLabel;
+ m_vcpkgLicense = new QLabel;
+ m_vcpkgDescription = new QTextBrowser;
+
+ m_vcpkgHomepage = new QLabel;
+ m_vcpkgHomepage->setOpenExternalLinks(true);
+ m_vcpkgHomepage->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
+ m_vcpkgHomepage->setTextInteractionFlags(Qt::TextBrowserInteraction);
+
+ m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Close);
+
+ using namespace Utils::Layouting;
+ Column {
+ Row {
+ Column {
+ m_packagesFilter,
+ m_packagesList,
+ },
+ Form {
+ Tr::tr("Nameļ¼"), m_vcpkgName, br,
+ Tr::tr("Version:"), m_vcpkgVersion, br,
+ Tr::tr("License:"), m_vcpkgLicense, br,
+ Tr::tr("Description:"), m_vcpkgDescription, br,
+ Tr::tr("Homepage:"), m_vcpkgHomepage, br,
+ },
+ },
+ m_buttonBox,
+ }.attachTo(this);
+
+ m_allPackages = vcpkgManifests(VcpkgSettings::instance()->vcpkgRoot.filePath());
+
+ listPackages({});
+
+ connect(m_packagesFilter, &FancyLineEdit::filterChanged,
+ this, &VcpkgPackageSearchDialog::listPackages);
+ connect(m_packagesList, &ListWidget::currentTextChanged,
+ this, &VcpkgPackageSearchDialog::showPackageDetails);
+ connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+}
+
+VcpkgManifest VcpkgPackageSearchDialog::selectedPackage() const
+{
+ return m_selectedPackage;
+}
+
+void VcpkgPackageSearchDialog::listPackages(const QString &filter)
+{
+ const VcpkgManifests filteredPackages = filtered(m_allPackages,
+ [&filter] (const VcpkgManifest &package) {
+ return filter.isEmpty()
+ || package.name.contains(filter, Qt::CaseInsensitive)
+ || package.shortDescription.contains(filter, Qt::CaseInsensitive)
+ || package.description.contains(filter, Qt::CaseInsensitive);
+ });
+ QStringList names = transform(filteredPackages, [] (const VcpkgManifest &package) {
+ return package.name;
+ });
+ names.sort();
+ m_packagesList->clear();
+ m_packagesList->addItems(names);
+}
+
+void VcpkgPackageSearchDialog::showPackageDetails(const QString &packageName)
+{
+ const VcpkgManifest manifest = findOrDefault(m_allPackages,
+ [&packageName] (const VcpkgManifest &m) {
+ return m.name == packageName;
+ });
+
+ m_vcpkgName->setText(manifest.name);
+ m_vcpkgVersion->setText(manifest.version);
+ m_vcpkgLicense->setText(manifest.license);
+ QString description = manifest.shortDescription;
+ if (!manifest.description.isEmpty())
+ description.append("" + manifest.description.join("
") + "
");
+ m_vcpkgDescription->setText(description);
+ m_vcpkgHomepage->setText(QString::fromLatin1("%1")
+ .arg(manifest.homepage.toDisplayString()));
+
+ m_selectedPackage = manifest;
+ m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!manifest.name.isEmpty());
+}
+
+VcpkgManifest parseVcpkgManifest(const QByteArray &vcpkgManifestJsonData, bool *ok)
+{
+ // https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-json
+ VcpkgManifest result;
+ const QJsonObject jsonObject = QJsonDocument::fromJson(vcpkgManifestJsonData).object();
+ if (const QJsonValue name = jsonObject.value("name"); !name.isUndefined())
+ result.name = name.toString();
+ for (const char *key : {"version", "version-semver", "version-date", "version-string"} ) {
+ if (const QJsonValue ver = jsonObject.value(QLatin1String(key)); !ver.isUndefined()) {
+ result.version = ver.toString();
+ break;
+ }
+ }
+ if (const QJsonValue license = jsonObject.value("license"); !license.isUndefined())
+ result.license = license.toString();
+ if (const QJsonValue description = jsonObject.value("description"); !description.isUndefined()) {
+ if (description.isArray()) {
+ const QJsonArray descriptionLines = description.toArray();
+ for (const QJsonValue &val : descriptionLines) {
+ const QString line = val.toString();
+ if (result.shortDescription.isEmpty()) {
+ result.shortDescription = line;
+ continue;
+ }
+ result.description.append(line);
+ }
+ } else {
+ result.shortDescription = description.toString();
+ }
+ }
+ if (const QJsonValue homepage = jsonObject.value("homepage"); !homepage.isUndefined())
+ result.homepage = QUrl::fromUserInput(homepage.toString());
+
+ if (ok)
+ *ok = !(result.name.isEmpty() || result.version.isEmpty());
+
+ return result;
+}
+
+VcpkgManifests vcpkgManifests(const FilePath &vcpkgRoot)
+{
+ const FilePath portsDir = vcpkgRoot / "ports";
+ VcpkgManifests result;
+ const FilePaths manifestFiles =
+ portsDir.dirEntries({{"vcpkg.json"}, QDir::Files, QDirIterator::Subdirectories});
+ for (const FilePath &manifestFile : manifestFiles) {
+ FileReader reader;
+ if (reader.fetch(manifestFile)) {
+ const QByteArray &manifestData = reader.data();
+ const VcpkgManifest manifest = parseVcpkgManifest(manifestData);
+ result.append(manifest);
+ }
+ }
+ return result;
+}
+
+VcpkgManifest showVcpkgPackageSearchDialog(QWidget *parent)
+{
+ VcpkgPackageSearchDialog dlg(parent ? parent : Core::ICore::dialogParent());
+ return (dlg.exec() == QDialog::Accepted) ? dlg.selectedPackage() : VcpkgManifest();
+}
+
+} // namespace Vcpkg::Internal::Search
diff --git a/src/plugins/vcpkg/vcpkgsearch.h b/src/plugins/vcpkg/vcpkgsearch.h
new file mode 100644
index 00000000000..bb2d568a00c
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgsearch.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include
+#include
+
+#include
+
+namespace Vcpkg::Internal::Search {
+
+struct VcpkgManifest
+{
+ QString name;
+ QString version;
+ QString license;
+ QString shortDescription;
+ QStringList description;
+ QUrl homepage;
+};
+
+using VcpkgManifests = QList;
+
+VcpkgManifest parseVcpkgManifest(const QByteArray &vcpkgManifestJsonData, bool *ok = nullptr);
+VcpkgManifests vcpkgManifests(const Utils::FilePath &vcpkgRoot);
+VcpkgManifest showVcpkgPackageSearchDialog(QWidget *parent = nullptr);
+
+} // namespace Vcpkg::Internal::Search
diff --git a/src/plugins/vcpkg/vcpkgsettings.cpp b/src/plugins/vcpkg/vcpkgsettings.cpp
new file mode 100644
index 00000000000..bea6c8ed6c7
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgsettings.cpp
@@ -0,0 +1,80 @@
+// 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 "vcpkgsettings.h"
+
+#include "vcpkgconstants.h"
+
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+namespace Vcpkg::Internal {
+
+VcpkgSettings::VcpkgSettings()
+{
+ setSettingsGroup("Vcpkg");
+
+ registerAspect(&vcpkgRoot);
+ vcpkgRoot.setSettingsKey("VcpkgRoot");
+ vcpkgRoot.setDisplayStyle(Utils::StringAspect::PathChooserDisplay);
+ vcpkgRoot.setExpectedKind(Utils::PathChooser::ExistingDirectory);
+ vcpkgRoot.setDefaultValue(Utils::qtcEnvironmentVariable(Constants::ENVVAR_VCPKG_ROOT));
+
+ readSettings(Core::ICore::settings());
+}
+
+VcpkgSettings *VcpkgSettings::instance()
+{
+ static VcpkgSettings s;
+ return &s;
+}
+
+bool VcpkgSettings::vcpkgRootValid() const
+{
+ return (vcpkgRoot.filePath() / "vcpkg").withExecutableSuffix().isExecutableFile();
+}
+
+VcpkgSettingsPage::VcpkgSettingsPage()
+{
+ setId(Constants::TOOLSSETTINGSPAGE_ID);
+ setDisplayName("Vcpkg");
+ setCategory(CMakeProjectManager::Constants::Settings::CATEGORY);
+
+ setLayouter([] (QWidget *widget) {
+ auto websiteButton = new QToolButton;
+ websiteButton->setIcon(Utils::Icons::ONLINE.icon());
+ websiteButton->setToolTip(Constants::WEBSITE_URL);
+
+ using namespace Utils::Layouting;
+ Column {
+ Group {
+ title(tr("Vcpkg installation")),
+ Form {
+ Utils::PathChooser::label(),
+ Span{ 2, Row{ VcpkgSettings::instance()->vcpkgRoot, websiteButton} },
+ },
+ },
+ st,
+ }.attachTo(widget);
+
+ connect(websiteButton, &QAbstractButton::clicked, [] {
+ QDesktopServices::openUrl(QUrl::fromUserInput(Constants::WEBSITE_URL));
+ });
+ });
+}
+
+void VcpkgSettingsPage::apply()
+{
+ VcpkgSettings::instance()->writeSettings(Core::ICore::settings());
+}
+
+} // namespace Vcpkg::Internal
diff --git a/src/plugins/vcpkg/vcpkgsettings.h b/src/plugins/vcpkg/vcpkgsettings.h
new file mode 100644
index 00000000000..a0a7973cc01
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgsettings.h
@@ -0,0 +1,30 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include
+#include
+
+namespace Vcpkg::Internal {
+
+class VcpkgSettings : public Utils::AspectContainer
+{
+public:
+ VcpkgSettings();
+
+ static VcpkgSettings *instance();
+ bool vcpkgRootValid() const;
+
+ Utils::StringAspect vcpkgRoot;
+};
+
+class VcpkgSettingsPage final : public Core::IOptionsPage
+{
+public:
+ VcpkgSettingsPage();
+
+ void apply() override;
+};
+
+} // namespace Vcpkg::Internal
diff --git a/src/plugins/vcpkg/vcpkgtr.h b/src/plugins/vcpkg/vcpkgtr.h
new file mode 100644
index 00000000000..0914ce6444d
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgtr.h
@@ -0,0 +1,15 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include
+
+namespace Vcpkg {
+
+struct Tr
+{
+ Q_DECLARE_TR_FUNCTIONS(Vcpkg)
+};
+
+} // namespace Vcpkg
diff --git a/src/plugins/vcpkg/wizards/manifest/vcpkg.json.tpl b/src/plugins/vcpkg/wizards/manifest/vcpkg.json.tpl
new file mode 100644
index 00000000000..1519949d2ea
--- /dev/null
+++ b/src/plugins/vcpkg/wizards/manifest/vcpkg.json.tpl
@@ -0,0 +1,6 @@
+{
+ "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
+ "name": "%{Name}",
+ "version-string": "%{VersionString}",
+%{Dependencies}
+}
diff --git a/src/plugins/vcpkg/wizards/manifest/wizard.json b/src/plugins/vcpkg/wizards/manifest/wizard.json
new file mode 100644
index 00000000000..80e8b0fcc52
--- /dev/null
+++ b/src/plugins/vcpkg/wizards/manifest/wizard.json
@@ -0,0 +1,80 @@
+{
+ "version": 1,
+ "supportedProjectTypes": [ ],
+ "id": "VcpkgManifest.Json",
+ "category": "U.VcpkgManifest",
+ "trDescription": "Creates a vcpkg.json manifest file.",
+ "trDisplayName": "vcpkg.json Manifest File",
+ "trDisplayCategory": "vcpkg",
+ "iconText": "json",
+
+ "options": [
+ { "key": "InitialFileName", "value": "vcpkg.json" },
+ { "key": "TargetPath", "value": "%{Path}" }
+ ],
+
+ "pages":
+ [
+ {
+ "trDisplayName": "Location",
+ "trShortTitle": "Location",
+ "typeId": "File"
+ },
+ {
+ "trDisplayName": "vcpkg.json Manifest File",
+ "trShortTitle": "Manifest fields",
+ "typeId": "Fields",
+ "data":
+ [
+ {
+ "name": "Name",
+ "trDisplayName": "Name:",
+ "mandatory": true,
+ "type": "LineEdit",
+ "data":
+ {
+ "trText": "mypackage",
+ "validator": "^[a-z_0-9]+$"
+ }
+ },
+ {
+ "name": "VersionString",
+ "trDisplayName": "Version string:",
+ "mandatory": true,
+ "type": "LineEdit",
+ "data":
+ {
+ "trText": "0.0.1"
+ }
+ },
+ {
+ "name": "Dependencies",
+ "trDisplayName": "Dependencies:",
+ "mandatory": false,
+ "type": "TextEdit",
+ "data":
+ {
+ "trText": " \"dependencies\": [\n \"fmt\"\n ]"
+ }
+ }
+ ]
+ },
+ {
+ "trDisplayName": "Project Management",
+ "trShortTitle": "Summary",
+ "typeId": "Summary"
+ }
+ ],
+ "generators":
+ [
+ {
+ "typeId": "File",
+ "data":
+ {
+ "source": "vcpkg.json.tpl",
+ "target": "%{Path}/vcpkg.json",
+ "openInEditor": true
+ }
+ }
+ ]
+}