diff --git a/azure-pipelines/e2e-ports/vcpkg-license-bsd-on-mit/portfile.cmake b/azure-pipelines/e2e-ports/vcpkg-license-bsd-on-mit/portfile.cmake new file mode 100644 index 0000000000..065116c276 --- /dev/null +++ b/azure-pipelines/e2e-ports/vcpkg-license-bsd-on-mit/portfile.cmake @@ -0,0 +1 @@ +set(VCPKG_POLICY_EMPTY_PACKAGE enabled) diff --git a/azure-pipelines/e2e-ports/vcpkg-license-bsd-on-mit/vcpkg.json b/azure-pipelines/e2e-ports/vcpkg-license-bsd-on-mit/vcpkg.json new file mode 100644 index 0000000000..f1e5cce8ed --- /dev/null +++ b/azure-pipelines/e2e-ports/vcpkg-license-bsd-on-mit/vcpkg.json @@ -0,0 +1,8 @@ +{ + "name": "vcpkg-license-bsd-on-mit", + "version": "0", + "license": "BSD-3-Clause", + "dependencies": [ + "vcpkg-license-mit" + ] +} diff --git a/azure-pipelines/e2e-ports/vcpkg-license-bsd/portfile.cmake b/azure-pipelines/e2e-ports/vcpkg-license-bsd/portfile.cmake new file mode 100644 index 0000000000..065116c276 --- /dev/null +++ b/azure-pipelines/e2e-ports/vcpkg-license-bsd/portfile.cmake @@ -0,0 +1 @@ +set(VCPKG_POLICY_EMPTY_PACKAGE enabled) diff --git a/azure-pipelines/e2e-ports/vcpkg-license-bsd/vcpkg.json b/azure-pipelines/e2e-ports/vcpkg-license-bsd/vcpkg.json new file mode 100644 index 0000000000..8b0d5d6e55 --- /dev/null +++ b/azure-pipelines/e2e-ports/vcpkg-license-bsd/vcpkg.json @@ -0,0 +1,5 @@ +{ + "name": "vcpkg-license-bsd", + "version": "0", + "license": "BSD-3-Clause" +} diff --git a/azure-pipelines/e2e-ports/vcpkg-license-mit/portfile.cmake b/azure-pipelines/e2e-ports/vcpkg-license-mit/portfile.cmake new file mode 100644 index 0000000000..065116c276 --- /dev/null +++ b/azure-pipelines/e2e-ports/vcpkg-license-mit/portfile.cmake @@ -0,0 +1 @@ +set(VCPKG_POLICY_EMPTY_PACKAGE enabled) diff --git a/azure-pipelines/e2e-ports/vcpkg-license-mit/vcpkg.json b/azure-pipelines/e2e-ports/vcpkg-license-mit/vcpkg.json new file mode 100644 index 0000000000..814573e267 --- /dev/null +++ b/azure-pipelines/e2e-ports/vcpkg-license-mit/vcpkg.json @@ -0,0 +1,5 @@ +{ + "name": "vcpkg-license-mit", + "version": "0", + "license": "MIT" +} diff --git a/azure-pipelines/e2e-ports/vcpkg-license-null/portfile.cmake b/azure-pipelines/e2e-ports/vcpkg-license-null/portfile.cmake new file mode 100644 index 0000000000..065116c276 --- /dev/null +++ b/azure-pipelines/e2e-ports/vcpkg-license-null/portfile.cmake @@ -0,0 +1 @@ +set(VCPKG_POLICY_EMPTY_PACKAGE enabled) diff --git a/azure-pipelines/e2e-ports/vcpkg-license-null/vcpkg.json b/azure-pipelines/e2e-ports/vcpkg-license-null/vcpkg.json new file mode 100644 index 0000000000..54c73ca7bd --- /dev/null +++ b/azure-pipelines/e2e-ports/vcpkg-license-null/vcpkg.json @@ -0,0 +1,5 @@ +{ + "name": "vcpkg-license-null", + "version": "0", + "license": null +} diff --git a/azure-pipelines/end-to-end-tests-dir/build-test-ports.ps1 b/azure-pipelines/end-to-end-tests-dir/build-test-ports.ps1 index f54a926e0e..42bf5aadc8 100644 --- a/azure-pipelines/end-to-end-tests-dir/build-test-ports.ps1 +++ b/azure-pipelines/end-to-end-tests-dir/build-test-ports.ps1 @@ -58,9 +58,10 @@ The following packages are already installed: $output = Run-VcpkgAndCaptureOutput @commonArgs --overlay-ports="$PSScriptRoot/../e2e-ports" install vcpkg-internal-e2e-test-port3 --head Throw-IfFailed -if ($output -notmatch 'vcpkg-internal-e2e-test-port3:[^ ]+ is already installed -- not building from HEAD') { - throw 'Wrong already installed message for --head' -} +Throw-IfNonContains -Actual $output -Expected @" +The following packages are already installed, but were requested at --head version. Their installed contents will not be changed. To get updated versions, remove these packages first: + vcpkg-internal-e2e-test-port3: +"@ Refresh-TestRoot $output = Run-VcpkgAndCaptureOutput @commonArgs --x-builtin-ports-root="$PSScriptRoot/../e2e-ports" install vcpkg-bad-spdx-license diff --git a/azure-pipelines/end-to-end-tests-dir/license-report.ps1 b/azure-pipelines/end-to-end-tests-dir/license-report.ps1 new file mode 100644 index 0000000000..5be271c519 --- /dev/null +++ b/azure-pipelines/end-to-end-tests-dir/license-report.ps1 @@ -0,0 +1,90 @@ +. $PSScriptRoot/../end-to-end-tests-prelude.ps1 + +[string]$output = Run-VcpkgAndCaptureOutput @commonArgs license-report "--x-builtin-ports-root=$PSScriptRoot/../e2e-ports" +Throw-IfFailed +Throw-IfNonEqual -Actual $output -Expected @" +There are no installed packages, and thus no licenses of installed packages. Did you mean to install something first? + +"@ + +$output = Run-VcpkgAndCaptureOutput @commonArgs install "--x-builtin-ports-root=$PSScriptRoot/../e2e-ports" vcpkg-license-bsd vcpkg-license-mit +Throw-IfFailed +Throw-IfNonContains -Actual $output -Expected @" +Packages installed in this vcpkg installation declare the following licenses: +BSD-3-Clause +MIT +"@ + +$output = Run-VcpkgAndCaptureOutput @commonArgs license-report "--x-builtin-ports-root=$PSScriptRoot/../e2e-ports" +Throw-IfFailed +Throw-IfNonEqual -Actual $output -Expected @" +Installed contents are licensed to you by owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +Installed packages declare the following licenses: +BSD-3-Clause +MIT + +"@ + +# Note that the MIT license already is not displayed +$output = Run-VcpkgAndCaptureOutput @commonArgs install "--x-builtin-ports-root=$PSScriptRoot/../e2e-ports" vcpkg-license-bsd-on-mit +Throw-IfFailed +Throw-IfNonContains -Actual $output -Expected @" +Packages installed in this vcpkg installation declare the following licenses: +BSD-3-Clause +"@ + +$output = Run-VcpkgAndCaptureOutput @commonArgs license-report "--x-builtin-ports-root=$PSScriptRoot/../e2e-ports" +Throw-IfFailed +Throw-IfNonEqual -Actual $output -Expected @" +Installed contents are licensed to you by owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +Installed packages declare the following licenses: +BSD-3-Clause +MIT + +"@ + +# Empty port == no license field set at all +$output = Run-VcpkgAndCaptureOutput @commonArgs install "--x-builtin-ports-root=$PSScriptRoot/../e2e-ports" vcpkg-empty-port +Throw-IfFailed +Throw-IfNonContains -Actual $output -Expected @" +Installed contents are licensed to you by owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +Some packages did not declare an SPDX license. Check the ``copyright`` file for each package for more information about their licensing. +"@ + +$output = Run-VcpkgAndCaptureOutput @commonArgs license-report "--x-builtin-ports-root=$PSScriptRoot/../e2e-ports" +Throw-IfFailed +Throw-IfNonEqual -Actual $output -Expected @" +Installed contents are licensed to you by owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +Some packages did not declare an SPDX license. Check the ``copyright`` file for each package for more information about their licensing. +Installed packages declare the following licenses: +BSD-3-Clause +MIT + +"@ + +Run-Vcpkg @commonArgs remove "--x-builtin-ports-root=$PSScriptRoot/../e2e-ports" vcpkg-license-bsd +Throw-IfFailed + +# bsd-on-mit is still here so no change +$output = Run-VcpkgAndCaptureOutput @commonArgs license-report "--x-builtin-ports-root=$PSScriptRoot/../e2e-ports" +Throw-IfFailed +Throw-IfNonEqual -Actual $output -Expected @" +Installed contents are licensed to you by owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +Some packages did not declare an SPDX license. Check the ``copyright`` file for each package for more information about their licensing. +Installed packages declare the following licenses: +BSD-3-Clause +MIT + +"@ + +Run-Vcpkg @commonArgs remove "--x-builtin-ports-root=$PSScriptRoot/../e2e-ports" vcpkg-license-bsd-on-mit vcpkg-license-mit +Throw-IfFailed + +# Only unknown left +$output = Run-VcpkgAndCaptureOutput @commonArgs license-report "--x-builtin-ports-root=$PSScriptRoot/../e2e-ports" +Throw-IfFailed +Throw-IfNonEqual -Actual $output -Expected @" +Installed contents are licensed to you by owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +Some packages did not declare an SPDX license. Check the ``copyright`` file for each package for more information about their licensing. + +"@ diff --git a/azure-pipelines/end-to-end-tests-dir/versions.ps1 b/azure-pipelines/end-to-end-tests-dir/versions.ps1 index 2a5a4896fe..31799da837 100644 --- a/azure-pipelines/end-to-end-tests-dir/versions.ps1 +++ b/azure-pipelines/end-to-end-tests-dir/versions.ps1 @@ -89,7 +89,7 @@ Throw-IfNotFailed if ($output -notmatch @" warning: In octopus, 1.0 is completely new version, so the "port-version" field should be removed. Remove "port-version", commit that change, and try again. To skip this check, rerun with --skip-version-format-check . "@) { - throw "Expected detecting present port-version when a new version is added as bad" + throw "Expected detecting present port-version when a new version is added as bad" } Run-Vcpkg @portsRedirectArgsOK x-add-version octopus --skip-version-format-check @@ -105,7 +105,7 @@ Throw-IfNotFailed if ($output -notmatch @" warning: In octopus, 2.0 is completely new version, so the "port-version" field should be removed. Remove "port-version", commit that change, and try again. To skip this check, rerun with --skip-version-format-check . "@) { - throw "Expected detecting present port-version when a new version is added as bad" + throw "Expected detecting present port-version when a new version is added as bad" } Run-Vcpkg @portsRedirectArgsOK x-add-version octopus --skip-version-format-check @@ -121,7 +121,7 @@ Throw-IfNotFailed if ($output -notmatch @" warning: In octopus, the current "port-version" for 2.0 is 1, so the next added "port-version" should be 2, but the port declares "port-version" 3. Change "port-version" to 2, commit that change, and try again. To skip this check, rerun with --skip-version-format-check . "@) { - throw "Expected detecting present port-version when a new version is added as bad" + throw "Expected detecting present port-version when a new version is added as bad" } Run-Vcpkg @portsRedirectArgsOK x-add-version octopus --skip-version-format-check diff --git a/include/vcpkg/base/contractual-constants.h b/include/vcpkg/base/contractual-constants.h index 53f6954bf8..61f731ca11 100644 --- a/include/vcpkg/base/contractual-constants.h +++ b/include/vcpkg/base/contractual-constants.h @@ -344,6 +344,8 @@ namespace vcpkg inline constexpr StringLiteral FilePortfileDotCMake = "portfile.cmake"; inline constexpr StringLiteral FileShare = "share"; inline constexpr StringLiteral FileStatus = "status"; + inline constexpr StringLiteral FileStatusNew = "status-new"; + inline constexpr StringLiteral FileStatusOld = "status-old"; inline constexpr StringLiteral FileTools = "tools"; inline constexpr StringLiteral FileUpdates = "updates"; inline constexpr StringLiteral FileUsage = "usage"; diff --git a/include/vcpkg/base/message-data.inc.h b/include/vcpkg/base/message-data.inc.h index 74b40b73b1..a00359b005 100644 --- a/include/vcpkg/base/message-data.inc.h +++ b/include/vcpkg/base/message-data.inc.h @@ -137,12 +137,7 @@ DECLARE_MESSAGE(AllFormatArgsUnbalancedBraces, (msg::value), "example of {value} is 'foo bar {'", "unbalanced brace in format string \"{value}\"") -DECLARE_MESSAGE(AllPackagesAreUpdated, (), "", "All installed packages are up-to-date.") -DECLARE_MESSAGE(AlreadyInstalled, (msg::spec), "", "{spec} is already installed") -DECLARE_MESSAGE(AlreadyInstalledNotHead, - (msg::spec), - "'HEAD' means the most recent version of source code", - "{spec} is already installed -- not building from HEAD") +DECLARE_MESSAGE(AllPackagesAreUpdated, (), "", "No action taken because all installed packages are up-to-date.") DECLARE_MESSAGE(AManifest, (), "", "a manifest") DECLARE_MESSAGE(AMaximumOfOneAssetReadUrlCanBeSpecified, (), "", "a maximum of one asset read url can be specified.") DECLARE_MESSAGE(AMaximumOfOneAssetWriteUrlCanBeSpecified, (), "", "a maximum of one asset write url can be specified.") @@ -735,6 +730,7 @@ DECLARE_MESSAGE(CmdInstallExample1, "This is a command line, only the <> parts should be localized", "vcpkg install ...") DECLARE_MESSAGE(CmdIntegrateSynopsis, (), "", "Integrates vcpkg with machines, projects, or shells") +DECLARE_MESSAGE(CmdLicenseReportSynopsis, (), "", "Displays the declared licenses of all ports in the installed tree") DECLARE_MESSAGE(CmdListExample2, (), "This is a command line, only the part should be localized", @@ -1168,10 +1164,10 @@ DECLARE_MESSAGE(ExpectedCharacterHere, DECLARE_MESSAGE(ExpectedDefaultFeaturesList, (), "", "expected ',' or end of text in default features list") DECLARE_MESSAGE(ExpectedDependenciesList, (), "", "expected ',' or end of text in dependencies list") DECLARE_MESSAGE(ExpectedDigitsAfterDecimal, (), "", "Expected digits after the decimal point") +DECLARE_MESSAGE(ExpectedExplicitTriplet, (), "", "expected an explicit triplet") DECLARE_MESSAGE(ExpectedFailOrSkip, (), "", "expected 'fail', 'skip', or 'pass' here") DECLARE_MESSAGE(ExpectedFeatureListTerminal, (), "", "expected ',' or ']' in feature list") DECLARE_MESSAGE(ExpectedFeatureName, (), "", "expected feature name (must be lowercase, digits, '-')") -DECLARE_MESSAGE(ExpectedExplicitTriplet, (), "", "expected an explicit triplet") DECLARE_MESSAGE(ExpectedInstallStateField, (), "The values in ''s are locale-invariant", @@ -1734,6 +1730,11 @@ DECLARE_MESSAGE(InstallCopiedFile, "{path_source} -> {path_destination} done") DECLARE_MESSAGE(InstalledBy, (msg::path), "", "Installed by {path}") DECLARE_MESSAGE(InstalledPackages, (), "", "The following packages are already installed:") +DECLARE_MESSAGE(InstalledPackagesHead, + (), + "", + "The following packages are already installed, but were requested at --head version. Their installed " + "contents will not be changed. To get updated versions, remove these packages first:") DECLARE_MESSAGE(InstalledRequestedPackages, (), "", "All requested packages are currently installed.") DECLARE_MESSAGE(InstallFailed, (msg::path, msg::error_msg), "", "failed: {path}: {error_msg}") DECLARE_MESSAGE(InstallingMavenFileFailure, @@ -2147,6 +2148,11 @@ DECLARE_MESSAGE(NoInstalledPackages, (), "The name 'search' is the name of a command that is not localized.", "No packages are installed. Did you mean `search`?") +DECLARE_MESSAGE(NoInstalledPackagesLicenseReport, + (), + "", + "There are no installed packages, and thus no licenses of installed packages. Did you mean to install " + "something first?") DECLARE_MESSAGE(NonExactlyArgs, (msg::command_name, msg::expected, msg::actual), "{expected} and {actual} are integers", @@ -2214,6 +2220,21 @@ DECLARE_MESSAGE(OverwritingFile, (msg::path), "", "File {path} was already prese DECLARE_MESSAGE(PackageAbi, (msg::spec, msg::package_abi), "", "{spec} package ABI: {package_abi}") DECLARE_MESSAGE(PackageAlreadyRemoved, (msg::spec), "", "unable to remove {spec}: already removed") DECLARE_MESSAGE(PackageDiscoveryHeader, (), "", "Package Discovery") +DECLARE_MESSAGE(PackageLicenseSpdx, (), "", "Installed packages declare the following licenses:") +DECLARE_MESSAGE(PackageLicenseSpdxThisInstall, + (), + "", + "Packages installed in this vcpkg installation declare the following licenses:") +DECLARE_MESSAGE(PackageLicenseUnknown, + (), + "", + "Some packages did not declare an SPDX license. Check the `copyright` file for each package for more " + "information about their licensing.") +DECLARE_MESSAGE(PackageLicenseWarning, + (), + "", + "Installed contents are licensed to you by owners. Microsoft is not responsible for, nor does it grant " + "any licenses to, third-party packages.") DECLARE_MESSAGE(PackageManipulationHeader, (), "", "Package Manipulation") DECLARE_MESSAGE(PackageInfoHelp, (), "", "Display detailed information on packages") DECLARE_MESSAGE(PackageFailedtWhileExtracting, @@ -2719,6 +2740,10 @@ DECLARE_MESSAGE(ToRemovePackages, "", "To only remove outdated packages, run\n{command_name} remove --outdated") DECLARE_MESSAGE(TotalInstallTime, (msg::elapsed), "", "Total install time: {elapsed}") +DECLARE_MESSAGE(TotalInstallTimeSuccess, + (msg::elapsed), + "", + "All requested installations completed successfully in: {elapsed}") DECLARE_MESSAGE(ToUpdatePackages, (msg::command_name), "", diff --git a/include/vcpkg/commands.install.h b/include/vcpkg/commands.install.h index 22d7f8f394..58c2ec4e70 100644 --- a/include/vcpkg/commands.install.h +++ b/include/vcpkg/commands.install.h @@ -37,14 +37,23 @@ namespace vcpkg PackageSpec m_spec; }; + struct LicenseReport + { + bool any_unknown_licenses = false; + std::set named_licenses; + void print_license_report(const msg::MessageT<>& named_license_heading) const; + }; + struct InstallSummary { std::vector results; + ElapsedTime timing; + LicenseReport license_report; + bool failed = false; - LocalizedString format() const; + LocalizedString format_results() const; void print_failed() const; - std::string xunit_results() const; - bool failed() const; + void print_complete_message() const; }; struct InstallDir diff --git a/include/vcpkg/commands.license-report.h b/include/vcpkg/commands.license-report.h new file mode 100644 index 0000000000..75011c37b3 --- /dev/null +++ b/include/vcpkg/commands.license-report.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +namespace vcpkg +{ + extern const CommandMetadata CommandLicenseReportMetadata; + void command_license_report_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths); +} diff --git a/include/vcpkg/dependencies.h b/include/vcpkg/dependencies.h index 994952581f..cba7ec3821 100644 --- a/include/vcpkg/dependencies.h +++ b/include/vcpkg/dependencies.h @@ -209,7 +209,10 @@ namespace vcpkg struct FormattedPlan { bool has_removals = false; - LocalizedString text; + LocalizedString warning_text; + LocalizedString normal_text; + + LocalizedString all_text() const; }; FormattedPlan format_plan(const ActionPlan& action_plan, const Path& builtin_ports_dir); diff --git a/include/vcpkg/spdx.h b/include/vcpkg/spdx.h index 09d9bf5749..480bf4374a 100644 --- a/include/vcpkg/spdx.h +++ b/include/vcpkg/spdx.h @@ -6,6 +6,7 @@ #include +#include #include #include @@ -29,5 +30,7 @@ namespace vcpkg std::string document_namespace, std::vector&& resource_docs); + Optional read_spdx_license(StringView text, StringView origin); + Json::Object run_resource_heuristics(StringView contents, StringView portRawVersion); } diff --git a/include/vcpkg/vcpkglib.h b/include/vcpkg/vcpkglib.h index 7621d8526b..6eeffbf4c3 100644 --- a/include/vcpkg/vcpkglib.h +++ b/include/vcpkg/vcpkglib.h @@ -13,7 +13,8 @@ namespace vcpkg { - StatusParagraphs database_load_check(const Filesystem& fs, const InstalledPaths& installed); + StatusParagraphs database_load(const ReadOnlyFilesystem& fs, const InstalledPaths& installed); + StatusParagraphs database_load_collapse(const Filesystem& fs, const InstalledPaths& installed); void write_update(const Filesystem& fs, const InstalledPaths& installed, const StatusParagraph& p); @@ -24,9 +25,12 @@ namespace vcpkg }; std::vector get_installed_ports(const StatusParagraphs& status_db); - std::vector get_installed_files(const Filesystem& fs, + std::vector get_installed_files(const ReadOnlyFilesystem& fs, const InstalledPaths& installed, const StatusParagraphs& status_db); + std::vector get_installed_files_and_upgrade(const Filesystem& fs, + const InstalledPaths& installed, + const StatusParagraphs& status_db); std::string shorten_text(StringView desc, const size_t length); } // namespace vcpkg diff --git a/locales/messages.json b/locales/messages.json index 1bb9ac7d94..ff0e4fbc0d 100644 --- a/locales/messages.json +++ b/locales/messages.json @@ -111,11 +111,7 @@ "_AllFormatArgsRawArgument.comment": "example of {value} is 'foo {} bar'", "AllFormatArgsUnbalancedBraces": "unbalanced brace in format string \"{value}\"", "_AllFormatArgsUnbalancedBraces.comment": "example of {value} is 'foo bar {'", - "AllPackagesAreUpdated": "All installed packages are up-to-date.", - "AlreadyInstalled": "{spec} is already installed", - "_AlreadyInstalled.comment": "An example of {spec} is zlib:x64-windows.", - "AlreadyInstalledNotHead": "{spec} is already installed -- not building from HEAD", - "_AlreadyInstalledNotHead.comment": "'HEAD' means the most recent version of source code An example of {spec} is zlib:x64-windows.", + "AllPackagesAreUpdated": "No action taken because all installed packages are up-to-date.", "AmbiguousConfigDeleteConfigFile": "Ambiguous vcpkg configuration provided by both manifest and configuration file.\n-- Delete configuration file {path}", "_AmbiguousConfigDeleteConfigFile.comment": "An example of {path} is /foo/bar.", "AnArrayOfDefaultFeatures": "an array of default features", @@ -430,6 +426,7 @@ "CmdInstallExample1": "vcpkg install ...", "_CmdInstallExample1.comment": "This is a command line, only the <> parts should be localized", "CmdIntegrateSynopsis": "Integrates vcpkg with machines, projects, or shells", + "CmdLicenseReportSynopsis": "Displays the declared licenses of all ports in the installed tree", "CmdListExample2": "vcpkg list ", "_CmdListExample2.comment": "This is a command line, only the part should be localized", "CmdNewExample1": "vcpkg new --name=example --version=1.0", @@ -972,6 +969,7 @@ "InstalledBy": "Installed by {path}", "_InstalledBy.comment": "An example of {path} is /foo/bar.", "InstalledPackages": "The following packages are already installed:", + "InstalledPackagesHead": "The following packages are already installed, but were requested at --head version. Their installed contents will not be changed. To get updated versions, remove these packages first:", "InstalledRequestedPackages": "All requested packages are currently installed.", "InstallingMavenFileFailure": "{path} installing Maven file, {command_line} failed with {exit_code}", "_InstallingMavenFileFailure.comment": "Printed after a maven install command fails An example of {path} is /foo/bar. An example of {command_line} is vcpkg install zlib. An example of {exit_code} is 127.", @@ -1185,6 +1183,7 @@ "NoError": "no error", "NoInstalledPackages": "No packages are installed. Did you mean `search`?", "_NoInstalledPackages.comment": "The name 'search' is the name of a command that is not localized.", + "NoInstalledPackagesLicenseReport": "There are no installed packages, and thus no licenses of installed packages. Did you mean to install something first?", "NoOutdatedPackages": "There are no outdated packages.", "NoRegistryForPort": "no registry configured for port {package_name}", "_NoRegistryForPort.comment": "An example of {package_name} is zlib.", @@ -1252,6 +1251,10 @@ "_PackageFailedtWhileExtracting.comment": "'{value}' is either a tool name or a package name. An example of {path} is /foo/bar.", "PackageInfoHelp": "Display detailed information on packages", "PackageInstallationHeader": "Package Installation", + "PackageLicenseSpdx": "Installed packages declare the following licenses:", + "PackageLicenseSpdxThisInstall": "Packages installed in this vcpkg installation declare the following licenses:", + "PackageLicenseUnknown": "Some packages did not declare an SPDX license. Check the `copyright` file for each package for more information about their licensing.", + "PackageLicenseWarning": "Installed contents are licensed to you by owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages.", "PackageManipulationHeader": "Package Manipulation", "PackageRootDir": "Packages directory (experimental)", "PackagesToInstall": "The following packages will be built and installed:", @@ -1440,6 +1443,8 @@ "_ToolOfVersionXNotFound.comment": "An example of {tool_name} is aria2. An example of {version} is 1.3.8.", "TotalInstallTime": "Total install time: {elapsed}", "_TotalInstallTime.comment": "An example of {elapsed} is 3.532 min.", + "TotalInstallTimeSuccess": "All requested installations completed successfully in: {elapsed}", + "_TotalInstallTimeSuccess.comment": "An example of {elapsed} is 3.532 min.", "TrailingCommaInArray": "Trailing comma in array", "TrailingCommaInObj": "Trailing comma in an object", "TripletFileNotFound": "Triplet file {triplet}.cmake not found", diff --git a/src/vcpkg-test/dependencies.cpp b/src/vcpkg-test/dependencies.cpp index 4a5e3457e1..d72a364e88 100644 --- a/src/vcpkg-test/dependencies.cpp +++ b/src/vcpkg-test/dependencies.cpp @@ -2406,25 +2406,25 @@ TEST_CASE ("formatting plan 1", "[dependencies]") { auto formatted = format_plan(plan, "/builtin"); CHECK_FALSE(formatted.has_removals); - CHECK(formatted.text == "All requested packages are currently installed.\n"); + CHECK(formatted.all_text() == "All requested packages are currently installed.\n"); } plan.remove_actions.push_back(remove_b); { auto formatted = format_plan(plan, "/builtin"); CHECK(formatted.has_removals); - CHECK(formatted.text == "The following packages will be removed:\n" - " b:x64-osx\n"); + CHECK(formatted.all_text() == "The following packages will be removed:\n" + " b:x64-osx\n"); } plan.remove_actions.push_back(remove_a); - REQUIRE_LINES(format_plan(plan, "/builtin").text, + REQUIRE_LINES(format_plan(plan, "/builtin").all_text(), "The following packages will be removed:\n" " a:x64-osx\n" " b:x64-osx\n"); plan.install_actions.push_back(std::move(install_c)); - REQUIRE_LINES(format_plan(plan, "/builtin").text, + REQUIRE_LINES(format_plan(plan, "/builtin").all_text(), "The following packages will be removed:\n" " a:x64-osx\n" " b:x64-osx\n" @@ -2432,7 +2432,7 @@ TEST_CASE ("formatting plan 1", "[dependencies]") " c:x64-osx@1 -- c\n"); plan.remove_actions.push_back(remove_c); - REQUIRE_LINES(format_plan(plan, "c").text, + REQUIRE_LINES(format_plan(plan, "c").all_text(), "The following packages will be removed:\n" " a:x64-osx\n" " b:x64-osx\n" @@ -2440,7 +2440,7 @@ TEST_CASE ("formatting plan 1", "[dependencies]") " c:x64-osx@1\n"); plan.install_actions.push_back(std::move(install_b)); - REQUIRE_LINES(format_plan(plan, "c").text, + REQUIRE_LINES(format_plan(plan, "c").all_text(), "The following packages will be removed:\n" " a:x64-osx\n" "The following packages will be rebuilt:\n" @@ -2454,7 +2454,7 @@ TEST_CASE ("formatting plan 1", "[dependencies]") { auto formatted = format_plan(plan, "b"); CHECK(formatted.has_removals); - REQUIRE_LINES(formatted.text, + REQUIRE_LINES(formatted.all_text(), "The following packages are already installed:\n" " * d:x86-windows@1\n" " e:x86-windows@1\n" @@ -2466,7 +2466,7 @@ TEST_CASE ("formatting plan 1", "[dependencies]") } plan.install_actions.push_back(std::move(install_f)); - REQUIRE_LINES(format_plan(plan, "b").text, + REQUIRE_LINES(format_plan(plan, "b").all_text(), "The following packages are excluded:\n" " f:x64-osx@1 -- f\n" "The following packages are already installed:\n" diff --git a/src/vcpkg-test/spdx.cpp b/src/vcpkg-test/spdx.cpp index efe68a3c71..b6da7cfdce 100644 --- a/src/vcpkg-test/spdx.cpp +++ b/src/vcpkg-test/spdx.cpp @@ -33,7 +33,7 @@ TEST_CASE ("spdx maximum serialization", "[spdx]") "https://test-document-namespace", {}); - auto expected = Json::parse(R"json( + static constexpr StringLiteral expected_text = R"json( { "$schema": "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json", "spdxVersion": "SPDX-2.2", @@ -157,12 +157,13 @@ TEST_CASE ("spdx maximum serialization", "[spdx]") "copyrightText": "NOASSERTION" } ] -})json", - "test") - .value(VCPKG_LINE_INFO); +})json"; + auto expected = Json::parse(expected_text, "test").value(VCPKG_LINE_INFO); auto doc = Json::parse(sbom, "test").value(VCPKG_LINE_INFO); Test::check_json_eq(expected.value, doc.value); + + CHECK(read_spdx_license(expected_text, "test") == "MIT"); } TEST_CASE ("spdx minimum serialization", "[spdx]") @@ -187,7 +188,7 @@ TEST_CASE ("spdx minimum serialization", "[spdx]") "https://test-document-namespace-2", {}); - auto expected = Json::parse(R"json( + static constexpr StringLiteral expected_text = R"json( { "$schema": "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json", "spdxVersion": "SPDX-2.2", @@ -286,12 +287,12 @@ TEST_CASE ("spdx minimum serialization", "[spdx]") "copyrightText": "NOASSERTION" } ] -})json", - "test") - .value(VCPKG_LINE_INFO); +})json"; + auto expected = Json::parse(expected_text, "test").value(VCPKG_LINE_INFO); auto doc = Json::parse(sbom, "test").value(VCPKG_LINE_INFO); Test::check_json_eq(expected.value, doc.value); + CHECK(!read_spdx_license(expected_text, "test").has_value()); } TEST_CASE ("spdx concat resources", "[spdx]") @@ -396,3 +397,622 @@ TEST_CASE ("spdx concat resources", "[spdx]") auto doc = Json::parse(sbom, "test").value(VCPKG_LINE_INFO); Test::check_json_eq(expected.value, doc.value); } + +TEST_CASE ("spdx license parse edge cases", "[spdx]") +{ + CHECK(!read_spdx_license("this is not json", "test").has_value()); + + static constexpr StringLiteral missing_packages = R"json( +{ + "$schema": "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json", + "spdxVersion": "SPDX-2.2", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "documentNamespace": "https://test-document-namespace-2", + "name": "zlib:arm-uwp@1.0 deadbeef", + "creationInfo": { + "creators": [ + "Tool: vcpkg-2999-12-31-unknownhash" + ], + "created": "now+1" + }, + "relationships": [ + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "GENERATES", + "relatedSpdxElement": "SPDXRef-binary" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-0" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-1" + }, + { + "spdxElementId": "SPDXRef-binary", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "DEPENDENCY_MANIFEST_OF", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-1", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + } + ], + "files": [ + { + "fileName": "./vcpkg.json", + "SPDXID": "SPDXRef-file-0", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-vcpkg.json" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + }, + { + "fileName": "./portfile.cmake", + "SPDXID": "SPDXRef-file-1", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-portfile.cmake" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + } + ] +})json"; + + CHECK(!read_spdx_license(missing_packages, "test").has_value()); + + static constexpr StringLiteral empty_packages = R"json( +{ + "$schema": "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json", + "spdxVersion": "SPDX-2.2", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "documentNamespace": "https://test-document-namespace-2", + "name": "zlib:arm-uwp@1.0 deadbeef", + "creationInfo": { + "creators": [ + "Tool: vcpkg-2999-12-31-unknownhash" + ], + "created": "now+1" + }, + "relationships": [ + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "GENERATES", + "relatedSpdxElement": "SPDXRef-binary" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-0" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-1" + }, + { + "spdxElementId": "SPDXRef-binary", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "DEPENDENCY_MANIFEST_OF", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-1", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + } + ], + "packages": [], + "files": [ + { + "fileName": "./vcpkg.json", + "SPDXID": "SPDXRef-file-0", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-vcpkg.json" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + }, + { + "fileName": "./portfile.cmake", + "SPDXID": "SPDXRef-file-1", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-portfile.cmake" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + } + ] +})json"; + + CHECK(!read_spdx_license(empty_packages, "test").has_value()); + + static constexpr StringLiteral wrong_packages_type = R"json( +{ + "$schema": "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json", + "spdxVersion": "SPDX-2.2", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "documentNamespace": "https://test-document-namespace-2", + "name": "zlib:arm-uwp@1.0 deadbeef", + "creationInfo": { + "creators": [ + "Tool: vcpkg-2999-12-31-unknownhash" + ], + "created": "now+1" + }, + "relationships": [ + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "GENERATES", + "relatedSpdxElement": "SPDXRef-binary" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-0" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-1" + }, + { + "spdxElementId": "SPDXRef-binary", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "DEPENDENCY_MANIFEST_OF", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-1", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + } + ], + "packages": {}, + "files": [ + { + "fileName": "./vcpkg.json", + "SPDXID": "SPDXRef-file-0", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-vcpkg.json" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + }, + { + "fileName": "./portfile.cmake", + "SPDXID": "SPDXRef-file-1", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-portfile.cmake" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + } + ] +})json"; + + CHECK(!read_spdx_license(wrong_packages_type, "test").has_value()); + + static constexpr StringLiteral wrong_packages_zero_type = R"json( +{ + "$schema": "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json", + "spdxVersion": "SPDX-2.2", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "documentNamespace": "https://test-document-namespace-2", + "name": "zlib:arm-uwp@1.0 deadbeef", + "creationInfo": { + "creators": [ + "Tool: vcpkg-2999-12-31-unknownhash" + ], + "created": "now+1" + }, + "relationships": [ + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "GENERATES", + "relatedSpdxElement": "SPDXRef-binary" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-0" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-1" + }, + { + "spdxElementId": "SPDXRef-binary", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "DEPENDENCY_MANIFEST_OF", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-1", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + } + ], + "packages": [42], + "files": [ + { + "fileName": "./vcpkg.json", + "SPDXID": "SPDXRef-file-0", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-vcpkg.json" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + }, + { + "fileName": "./portfile.cmake", + "SPDXID": "SPDXRef-file-1", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-portfile.cmake" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + } + ] +})json"; + + CHECK(!read_spdx_license(wrong_packages_zero_type, "test").has_value()); + + static constexpr StringLiteral missing_license_block = R"json( +{ + "$schema": "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json", + "spdxVersion": "SPDX-2.2", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "documentNamespace": "https://test-document-namespace-2", + "name": "zlib:arm-uwp@1.0 deadbeef", + "creationInfo": { + "creators": [ + "Tool: vcpkg-2999-12-31-unknownhash" + ], + "created": "now+1" + }, + "relationships": [ + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "GENERATES", + "relatedSpdxElement": "SPDXRef-binary" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-0" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-1" + }, + { + "spdxElementId": "SPDXRef-binary", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "DEPENDENCY_MANIFEST_OF", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-1", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + } + ], + "packages": [ + { + "name": "zlib", + "SPDXID": "SPDXRef-port", + "versionInfo": "1.0", + "downloadLocation": "NOASSERTION", + "copyrightText": "NOASSERTION", + "comment": "This is the port (recipe) consumed by vcpkg." + } + ], + "files": [ + { + "fileName": "./vcpkg.json", + "SPDXID": "SPDXRef-file-0", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-vcpkg.json" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + }, + { + "fileName": "./portfile.cmake", + "SPDXID": "SPDXRef-file-1", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-portfile.cmake" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + } + ] +})json"; + + CHECK(!read_spdx_license(missing_license_block, "test").has_value()); + + static constexpr StringLiteral wrong_license_type = R"json( +{ + "$schema": "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json", + "spdxVersion": "SPDX-2.2", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "documentNamespace": "https://test-document-namespace-2", + "name": "zlib:arm-uwp@1.0 deadbeef", + "creationInfo": { + "creators": [ + "Tool: vcpkg-2999-12-31-unknownhash" + ], + "created": "now+1" + }, + "relationships": [ + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "GENERATES", + "relatedSpdxElement": "SPDXRef-binary" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-0" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-1" + }, + { + "spdxElementId": "SPDXRef-binary", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "DEPENDENCY_MANIFEST_OF", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-1", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + } + ], + "packages": [ + { + "name": "zlib", + "SPDXID": "SPDXRef-port", + "versionInfo": "1.0", + "downloadLocation": "NOASSERTION", + "licenseConcluded": 42, + "licenseDeclared": 42, + "copyrightText": "NOASSERTION", + "comment": "This is the port (recipe) consumed by vcpkg." + } + ], + "files": [ + { + "fileName": "./vcpkg.json", + "SPDXID": "SPDXRef-file-0", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-vcpkg.json" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + }, + { + "fileName": "./portfile.cmake", + "SPDXID": "SPDXRef-file-1", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-portfile.cmake" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + } + ] +})json"; + + CHECK(!read_spdx_license(wrong_license_type, "test").has_value()); + + static constexpr StringLiteral empty_license = R"json( +{ + "$schema": "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json", + "spdxVersion": "SPDX-2.2", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "documentNamespace": "https://test-document-namespace-2", + "name": "zlib:arm-uwp@1.0 deadbeef", + "creationInfo": { + "creators": [ + "Tool: vcpkg-2999-12-31-unknownhash" + ], + "created": "now+1" + }, + "relationships": [ + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "GENERATES", + "relatedSpdxElement": "SPDXRef-binary" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-0" + }, + { + "spdxElementId": "SPDXRef-port", + "relationshipType": "CONTAINS", + "relatedSpdxElement": "SPDXRef-file-1" + }, + { + "spdxElementId": "SPDXRef-binary", + "relationshipType": "GENERATED_FROM", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-0", + "relationshipType": "DEPENDENCY_MANIFEST_OF", + "relatedSpdxElement": "SPDXRef-port" + }, + { + "spdxElementId": "SPDXRef-file-1", + "relationshipType": "CONTAINED_BY", + "relatedSpdxElement": "SPDXRef-port" + } + ], + "packages": [ + { + "name": "zlib", + "SPDXID": "SPDXRef-port", + "versionInfo": "1.0", + "downloadLocation": "NOASSERTION", + "licenseConcluded": "", + "licenseDeclared": "", + "copyrightText": "NOASSERTION", + "comment": "This is the port (recipe) consumed by vcpkg." + }, + { + "name": "zlib:arm-uwp", + "SPDXID": "SPDXRef-binary", + "versionInfo": "deadbeef", + "downloadLocation": "NONE", + "licenseConcluded": "NOASSERTION", + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "comment": "This is a binary package built by vcpkg." + } + ], + "files": [ + { + "fileName": "./vcpkg.json", + "SPDXID": "SPDXRef-file-0", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-vcpkg.json" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + }, + { + "fileName": "./portfile.cmake", + "SPDXID": "SPDXRef-file-1", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "hash-portfile.cmake" + } + ], + "licenseConcluded": "NOASSERTION", + "copyrightText": "NOASSERTION" + } + ] +})json"; + + CHECK(!read_spdx_license(empty_license, "test").has_value()); +} diff --git a/src/vcpkg/commands.build.cpp b/src/vcpkg/commands.build.cpp index db1b21c772..16029b5612 100644 --- a/src/vcpkg/commands.build.cpp +++ b/src/vcpkg/commands.build.cpp @@ -130,7 +130,7 @@ namespace vcpkg auto& var_provider = *var_provider_storage; var_provider.load_dep_info_vars({{spec}}, host_triplet); - StatusParagraphs status_db = database_load_check(paths.get_filesystem(), paths.installed()); + StatusParagraphs status_db = database_load_collapse(paths.get_filesystem(), paths.installed()); auto action_plan = create_feature_install_plan( provider, var_provider, diff --git a/src/vcpkg/commands.ci.cpp b/src/vcpkg/commands.ci.cpp index 35199bbcaa..c47ebf0acb 100644 --- a/src/vcpkg/commands.ci.cpp +++ b/src/vcpkg/commands.ci.cpp @@ -500,7 +500,7 @@ namespace vcpkg } else { - StatusParagraphs status_db = database_load_check(paths.get_filesystem(), paths.installed()); + StatusParagraphs status_db = database_load_collapse(paths.get_filesystem(), paths.installed()); auto already_installed = adjust_action_plan_to_status_db(action_plan, status_db); Util::erase_if(already_installed, [&](auto& spec) { return Util::Sets::contains(split_specs->known, spec); }); @@ -530,7 +530,7 @@ namespace vcpkg .append_raw(' ') .append_raw(target_triplet) .append_raw('\n') - .append(summary.format())); + .append(summary.format_results())); const bool any_regressions = print_regressions(summary.results, split_specs->known, cidata, diff --git a/src/vcpkg/commands.cpp b/src/vcpkg/commands.cpp index d70ab16afb..8832bb5ff5 100644 --- a/src/vcpkg/commands.cpp +++ b/src/vcpkg/commands.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -92,6 +93,7 @@ namespace vcpkg {CommandFormatManifestMetadata, command_format_manifest_and_exit}, {CommandHelpMetadata, command_help_and_exit}, {CommandIntegrateMetadata, command_integrate_and_exit}, + {CommandLicenseReportMetadata, command_license_report_and_exit}, {CommandListMetadata, command_list_and_exit}, {CommandNewMetadata, command_new_and_exit}, {CommandOwnsMetadata, command_owns_and_exit}, diff --git a/src/vcpkg/commands.export.cpp b/src/vcpkg/commands.export.cpp index 338c35602c..698d96a9f5 100644 --- a/src/vcpkg/commands.export.cpp +++ b/src/vcpkg/commands.export.cpp @@ -596,7 +596,7 @@ namespace vcpkg Triplet host_triplet) { (void)host_triplet; - const StatusParagraphs status_db = database_load_check(paths.get_filesystem(), paths.installed()); + const StatusParagraphs status_db = database_load(paths.get_filesystem(), paths.installed()); const auto opts = handle_export_command_arguments(paths, args, default_triplet, status_db); // Load ports from ports dirs diff --git a/src/vcpkg/commands.install.cpp b/src/vcpkg/commands.install.cpp index 5f750f3658..017138112b 100644 --- a/src/vcpkg/commands.install.cpp +++ b/src/vcpkg/commands.install.cpp @@ -224,7 +224,7 @@ namespace vcpkg const auto package_dir = paths.package_dir(bcf.core_paragraph.spec); Triplet triplet = bcf.core_paragraph.spec.triplet(); const std::vector pgh_and_files = - get_installed_files(fs, installed, *status_db); + get_installed_files_and_upgrade(fs, installed, *status_db); const SortedVector package_files = build_list_of_package_files(fs, package_dir); const SortedVector installed_files = build_list_of_installed_files(pgh_and_files, triplet); @@ -315,6 +315,27 @@ namespace vcpkg return InstallResult::SUCCESS; } + void LicenseReport::print_license_report(const msg::MessageT<>& named_license_heading) const + { + if (any_unknown_licenses || !named_licenses.empty()) + { + msg::println(msgPackageLicenseWarning); + if (any_unknown_licenses) + { + msg::println(msgPackageLicenseUnknown); + } + + if (!named_licenses.empty()) + { + msg::println(named_license_heading); + for (auto&& license : named_licenses) + { + msg::print(LocalizedString::from_raw(license).append_raw('\n')); + } + } + } + } + static ExtendedBuildResult perform_install_plan_action(const VcpkgCmdArguments& args, const VcpkgPaths& paths, Triplet host_triplet, @@ -328,10 +349,6 @@ namespace vcpkg const InstallPlanType& plan_type = action.plan_type; if (plan_type == InstallPlanType::ALREADY_INSTALLED) { - if (action.use_head_version == UseHeadVersion::Yes) - msg::println(Color::warning, msgAlreadyInstalledNotHead, msg::spec = action.spec); - else - msg::println(Color::success, msgAlreadyInstalled, msg::spec = action.spec); return ExtendedBuildResult{BuildResult::Succeeded}; } @@ -435,7 +452,7 @@ namespace vcpkg .append_raw(result.timing.to_string()); } - LocalizedString InstallSummary::format() const + LocalizedString InstallSummary::format_results() const { LocalizedString to_print; to_print.append(msgResultsHeader).append_raw('\n'); @@ -475,26 +492,16 @@ namespace vcpkg msg::print(output); } - bool InstallSummary::failed() const + void InstallSummary::print_complete_message() const { - for (const auto& result : this->results) + if (failed) { - switch (result.build_result.value_or_exit(VCPKG_LINE_INFO).code) - { - case BuildResult::Succeeded: - case BuildResult::Removed: - case BuildResult::Downloaded: - case BuildResult::Excluded: continue; - case BuildResult::BuildFailed: - case BuildResult::PostBuildChecksFailed: - case BuildResult::FileConflicts: - case BuildResult::CascadedDueToMissingDependencies: - case BuildResult::CacheMissing: return true; - default: Checks::unreachable(VCPKG_LINE_INFO); - } + msg::println(msgTotalInstallTime, msg::elapsed = timing); + } + else + { + msg::println(Color::success, msgTotalInstallTimeSuccess, msg::elapsed = timing); } - - return false; } struct TrackedPackageInstallGuard @@ -576,49 +583,82 @@ namespace vcpkg const IBuildLogsRecorder& build_logs_recorder, bool include_manifest_in_github_issue) { - const ElapsedTimer timer; - std::vector results; + ElapsedTimer timer; + InstallSummary summary; const size_t action_count = action_plan.remove_actions.size() + action_plan.install_actions.size(); size_t action_index = 1; auto& fs = paths.get_filesystem(); for (auto&& action : action_plan.remove_actions) { - TrackedPackageInstallGuard this_install(action_index++, action_count, results, action); + TrackedPackageInstallGuard this_install(action_index++, action_count, summary.results, action); remove_package(fs, paths.installed(), action.spec, status_db); - results.back().build_result.emplace(BuildResult::Removed); + summary.results.back().build_result.emplace(BuildResult::Removed); } for (auto&& action : action_plan.already_installed) { - results.emplace_back(action).build_result.emplace(perform_install_plan_action( + summary.results.emplace_back(action).build_result.emplace(perform_install_plan_action( args, paths, host_triplet, build_options, action, status_db, binary_cache, build_logs_recorder)); } for (auto&& action : action_plan.install_actions) { - TrackedPackageInstallGuard this_install(action_index++, action_count, results, action); + TrackedPackageInstallGuard this_install(action_index++, action_count, summary.results, action); auto result = perform_install_plan_action( args, paths, host_triplet, build_options, action, status_db, binary_cache, build_logs_recorder); - if (result.code != BuildResult::Succeeded && build_options.keep_going == KeepGoing::No) + if (result.code == BuildResult::Succeeded) { - this_install.print_elapsed_time(); - print_user_troubleshooting_message(action, paths, result.stdoutlog.then([&](auto&) -> Optional { - auto issue_body_path = paths.installed().root() / "vcpkg" / "issue_body.md"; - paths.get_filesystem().write_contents( - issue_body_path, - create_github_issue(args, result, paths, action, include_manifest_in_github_issue), - VCPKG_LINE_INFO); - return issue_body_path; - })); - Checks::exit_fail(VCPKG_LINE_INFO); + if (auto scfl = action.source_control_file_and_location.get()) + { + if (auto license = scfl->source_control_file->core_paragraph->license.get()) + { + summary.license_report.named_licenses.insert(*license); + } + else + { + summary.license_report.any_unknown_licenses = true; + } + } + } + else + { + if (build_options.keep_going == KeepGoing::No) + { + this_install.print_elapsed_time(); + print_user_troubleshooting_message( + action, paths, result.stdoutlog.then([&](auto&) -> Optional { + auto issue_body_path = paths.installed().root() / "vcpkg" / "issue_body.md"; + paths.get_filesystem().write_contents( + issue_body_path, + create_github_issue(args, result, paths, action, include_manifest_in_github_issue), + VCPKG_LINE_INFO); + return issue_body_path; + })); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + switch (result.code) + { + case BuildResult::Succeeded: + case BuildResult::Removed: + case BuildResult::Downloaded: + case BuildResult::Excluded: break; + case BuildResult::BuildFailed: + case BuildResult::PostBuildChecksFailed: + case BuildResult::FileConflicts: + case BuildResult::CascadedDueToMissingDependencies: + case BuildResult::CacheMissing: summary.failed = true; break; + default: Checks::unreachable(VCPKG_LINE_INFO); + } } this_install.current_summary.build_result.emplace(std::move(result)); } - msg::println(msgTotalInstallTime, msg::elapsed = timer.to_string()); - return InstallSummary{std::move(results)}; + database_load_collapse(fs, paths.installed()); + summary.timing = timer.elapsed(); + return summary; } static constexpr CommandSwitch INSTALL_SWITCHES[] = { @@ -1284,7 +1324,7 @@ namespace vcpkg // create the plan msg::println(msgComputingInstallPlan); - StatusParagraphs status_db = database_load_check(fs, paths.installed()); + StatusParagraphs status_db = database_load_collapse(fs, paths.installed()); // Note: action_plan will hold raw pointers to SourceControlFileLocations from this map auto action_plan = create_feature_install_plan(provider, var_provider, specs, status_db, create_options); @@ -1373,7 +1413,7 @@ namespace vcpkg // success. if (keep_going == KeepGoing::Yes) { - msg::print(summary.format()); + msg::print(summary.format_results()); } auto it_xunit = options.settings.find(SwitchXXUnit); @@ -1394,6 +1434,8 @@ namespace vcpkg fs.write_contents(it_xunit->second, xwriter.build_xml(default_triplet), VCPKG_LINE_INFO); } + summary.license_report.print_license_report(msgPackageLicenseSpdxThisInstall); + if (print_cmake_usage) { std::set printed_usages; @@ -1408,7 +1450,8 @@ namespace vcpkg } } - Checks::exit_with_code(VCPKG_LINE_INFO, summary.failed()); + summary.print_complete_message(); + Checks::exit_with_code(VCPKG_LINE_INFO, summary.failed); } SpecSummary::SpecSummary(const InstallPlanAction& action) diff --git a/src/vcpkg/commands.license-report.cpp b/src/vcpkg/commands.license-report.cpp new file mode 100644 index 0000000000..4add1032d7 --- /dev/null +++ b/src/vcpkg/commands.license-report.cpp @@ -0,0 +1,64 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace vcpkg; + +namespace vcpkg +{ + + constexpr CommandMetadata CommandLicenseReportMetadata{ + "license-report", + msgCmdLicenseReportSynopsis, + {"vcpkg license-report"}, + Undocumented, + AutocompletePriority::Public, + 0, + 0, + {}, + nullptr, + }; + + void command_license_report_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + (void)args.parse_arguments(CommandLicenseReportMetadata); + + auto&& fs = paths.get_filesystem(); + auto&& installed_paths = paths.installed(); + LicenseReport report; + auto status_paragraphs = database_load(fs, installed_paths); + auto installed_ipvs = get_installed_ports(status_paragraphs); + if (installed_ipvs.empty()) + { + msg::println(msgNoInstalledPackagesLicenseReport); + Checks::exit_success(VCPKG_LINE_INFO); + } + + for (auto&& installed_ipv : installed_ipvs) + { + auto spdx_file = installed_paths.spdx_file(installed_ipv.spec()); + auto maybe_spdx_content = fs.try_read_contents(spdx_file); + if (auto spdx_content = maybe_spdx_content.get()) + { + auto maybe_parsed_license = read_spdx_license(spdx_content->content, spdx_content->origin); + if (auto parsed_license = maybe_parsed_license.get()) + { + report.named_licenses.insert(*parsed_license); + continue; + } + } + + report.any_unknown_licenses = true; + } + + report.print_license_report(msgPackageLicenseSpdx); + Checks::exit_success(VCPKG_LINE_INFO); + } +} // namespace vcpkg diff --git a/src/vcpkg/commands.list.cpp b/src/vcpkg/commands.list.cpp index a7ff89e78b..0d4f4956b2 100644 --- a/src/vcpkg/commands.list.cpp +++ b/src/vcpkg/commands.list.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -103,7 +104,7 @@ namespace vcpkg msg::default_output_stream = OutputStream::StdErr; const ParsedArguments options = args.parse_arguments(CommandListMetadata); - const StatusParagraphs status_paragraphs = database_load_check(paths.get_filesystem(), paths.installed()); + const StatusParagraphs status_paragraphs = database_load(paths.get_filesystem(), paths.installed()); auto installed_ipv = get_installed_ports(status_paragraphs); const auto output_json = Util::Sets::contains(options.switches, SwitchXJson); diff --git a/src/vcpkg/commands.owns.cpp b/src/vcpkg/commands.owns.cpp index f960371c84..ca3e47c5ea 100644 --- a/src/vcpkg/commands.owns.cpp +++ b/src/vcpkg/commands.owns.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -8,7 +10,7 @@ using namespace vcpkg; namespace { - void search_file(const Filesystem& fs, + void search_file(const ReadOnlyFilesystem& fs, const InstalledPaths& installed, const std::string& file_substr, const StatusParagraphs& status_db) @@ -46,7 +48,7 @@ namespace vcpkg void command_owns_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { const auto parsed = args.parse_arguments(CommandOwnsMetadata); - const StatusParagraphs status_db = database_load_check(paths.get_filesystem(), paths.installed()); + const StatusParagraphs status_db = database_load(paths.get_filesystem(), paths.installed()); search_file(paths.get_filesystem(), paths.installed(), parsed.command_arguments[0], status_db); Checks::exit_success(VCPKG_LINE_INFO); } diff --git a/src/vcpkg/commands.package-info.cpp b/src/vcpkg/commands.package-info.cpp index bd94a6235a..2d8df043b8 100644 --- a/src/vcpkg/commands.package-info.cpp +++ b/src/vcpkg/commands.package-info.cpp @@ -61,7 +61,7 @@ namespace vcpkg auto& fs = paths.get_filesystem(); if (installed) { - const StatusParagraphs status_paragraphs = database_load_check(fs, paths.installed()); + const StatusParagraphs status_paragraphs = database_load(fs, paths.installed()); std::set specs_written; std::vector specs_to_write; for (auto&& arg : options.command_arguments) diff --git a/src/vcpkg/commands.remove.cpp b/src/vcpkg/commands.remove.cpp index 1495f34f8c..1b6d43965e 100644 --- a/src/vcpkg/commands.remove.cpp +++ b/src/vcpkg/commands.remove.cpp @@ -148,7 +148,7 @@ namespace std::vector valid_arguments(const VcpkgPaths& paths) { - const StatusParagraphs status_db = database_load_check(paths.get_filesystem(), paths.installed()); + const StatusParagraphs status_db = database_load(paths.get_filesystem(), paths.installed()); auto installed_packages = get_installed_ports(status_db); return Util::fmap(installed_packages, [](auto&& pgh) -> std::string { return pgh.spec().to_string(); }); @@ -181,7 +181,7 @@ namespace vcpkg } const ParsedArguments options = args.parse_arguments(CommandRemoveMetadata); - StatusParagraphs status_db = database_load_check(paths.get_filesystem(), paths.installed()); + StatusParagraphs status_db = database_load_collapse(paths.get_filesystem(), paths.installed()); std::vector specs; if (Util::Sets::contains(options.switches, SwitchOutdated)) { @@ -298,6 +298,7 @@ namespace vcpkg } } + database_load_collapse(fs, paths.installed()); Checks::exit_success(VCPKG_LINE_INFO); } } diff --git a/src/vcpkg/commands.set-installed.cpp b/src/vcpkg/commands.set-installed.cpp index 36371b6086..2a5e14b206 100644 --- a/src/vcpkg/commands.set-installed.cpp +++ b/src/vcpkg/commands.set-installed.cpp @@ -159,9 +159,19 @@ namespace vcpkg specs_installed.erase(action.spec); } - Util::erase_remove_if(action_plan.install_actions, [&](const InstallPlanAction& ipa) { - return Util::Sets::contains(specs_installed, ipa.spec); + Util::erase_remove_if(action_plan.install_actions, [&](InstallPlanAction& ipa) { + if (Util::Sets::contains(specs_installed, ipa.spec)) + { + // convert the 'to install' entry to an already installed entry + ipa.installed_package = status_db.get_installed_package_view(ipa.spec); + ipa.plan_type = InstallPlanType::ALREADY_INSTALLED; + action_plan.already_installed.push_back(std::move(ipa)); + return true; + } + + return false; }); + return specs_installed; } @@ -212,7 +222,7 @@ namespace vcpkg } // currently (or once) installed specifications - auto status_db = database_load_check(fs, paths.installed()); + auto status_db = database_load_collapse(fs, paths.installed()); adjust_action_plan_to_status_db(action_plan, status_db); print_plan(action_plan, paths.builtin_ports_directory()); @@ -249,7 +259,7 @@ namespace vcpkg null_build_logs_recorder(), include_manifest_in_github_issue); - if (build_options.keep_going == KeepGoing::Yes && summary.failed()) + if (build_options.keep_going == KeepGoing::Yes && summary.failed) { summary.print_failed(); if (build_options.only_downloads == OnlyDownloads::No) @@ -258,6 +268,8 @@ namespace vcpkg } } + summary.license_report.print_license_report(msgPackageLicenseSpdxThisInstall); + if (print_usage == PrintUsage::Yes) { // Note that this differs from the behavior of `vcpkg install` in that it will print usage information for @@ -273,6 +285,7 @@ namespace vcpkg } } + summary.print_complete_message(); Checks::exit_success(VCPKG_LINE_INFO); } diff --git a/src/vcpkg/commands.update.cpp b/src/vcpkg/commands.update.cpp index aaca543a48..8bdb5bdc2b 100644 --- a/src/vcpkg/commands.update.cpp +++ b/src/vcpkg/commands.update.cpp @@ -65,7 +65,7 @@ namespace vcpkg msg::println(msgLocalPortfileVersion); auto& fs = paths.get_filesystem(); - const StatusParagraphs status_db = database_load_check(fs, paths.installed()); + const StatusParagraphs status_db = database_load(fs, paths.installed()); auto registry_set = paths.make_registry_set(); PathsPortFileProvider provider(*registry_set, make_overlay_provider(fs, paths.overlay_ports)); diff --git a/src/vcpkg/commands.upgrade.cpp b/src/vcpkg/commands.upgrade.cpp index 95415d5252..b12bc3f37f 100644 --- a/src/vcpkg/commands.upgrade.cpp +++ b/src/vcpkg/commands.upgrade.cpp @@ -75,7 +75,7 @@ namespace vcpkg const CreateUpgradePlanOptions create_upgrade_plan_options{ nullptr, host_triplet, paths.packages(), unsupported_port_action}; - StatusParagraphs status_db = database_load_check(paths.get_filesystem(), paths.installed()); + StatusParagraphs status_db = database_load_collapse(paths.get_filesystem(), paths.installed()); // Load ports from ports dirs auto& fs = paths.get_filesystem(); @@ -209,11 +209,14 @@ namespace vcpkg const InstallSummary summary = install_execute_plan( args, paths, host_triplet, build_options, action_plan, status_db, binary_cache, null_build_logs_recorder()); + // Skip printing the summary without --keep-going because the status without it is 'obvious': everything was a + // success. if (keep_going == KeepGoing::Yes) { - msg::print(summary.format()); + msg::print(summary.format_results()); } + summary.print_complete_message(); Checks::exit_success(VCPKG_LINE_INFO); } } // namespace vcpkg diff --git a/src/vcpkg/dependencies.cpp b/src/vcpkg/dependencies.cpp index bfdd903138..0ef5e6d8d1 100644 --- a/src/vcpkg/dependencies.cpp +++ b/src/vcpkg/dependencies.cpp @@ -1226,14 +1226,21 @@ namespace vcpkg } } + LocalizedString FormattedPlan::all_text() const + { + auto result = warning_text; + result.append(normal_text); + return result; + } + FormattedPlan format_plan(const ActionPlan& action_plan, const Path& builtin_ports_dir) { FormattedPlan ret; if (action_plan.remove_actions.empty() && action_plan.already_installed.empty() && action_plan.install_actions.empty()) { - ret.text = msg::format(msgInstalledRequestedPackages); - ret.text.append_raw('\n'); + ret.normal_text = msg::format(msgInstalledRequestedPackages); + ret.normal_text.append_raw('\n'); return ret; } @@ -1241,6 +1248,7 @@ namespace vcpkg std::vector rebuilt_plans; std::vector new_plans; std::vector already_installed_plans; + std::vector already_installed_head_plans; std::vector excluded; const bool has_non_user_requested_packages = @@ -1248,6 +1256,20 @@ namespace vcpkg return action.request_type != RequestType::USER_REQUESTED; }) != action_plan.install_actions.cend(); + for (auto&& already_installed_action : action_plan.already_installed) + { + std::vector* to_add; + if (already_installed_action.use_head_version == UseHeadVersion::Yes) + { + to_add = &already_installed_head_plans; + } + else + { + to_add = &already_installed_plans; + } + + to_add->push_back(&already_installed_action); + } for (auto&& remove_action : action_plan.remove_actions) { remove_specs.emplace(remove_action.spec); @@ -1257,54 +1279,67 @@ namespace vcpkg // remove plans are guaranteed to come before install plans, so we know the plan will be contained // if at all. auto it = remove_specs.find(install_action.spec); - if (it != remove_specs.end()) + if (it == remove_specs.end()) { - remove_specs.erase(it); - rebuilt_plans.push_back(&install_action); + std::vector* to_add; + if (install_action.plan_type == InstallPlanType::EXCLUDED) + { + to_add = &excluded; + } + else + { + to_add = &new_plans; + } + + to_add->push_back(&install_action); } else { - if (install_action.plan_type == InstallPlanType::EXCLUDED) - excluded.push_back(&install_action); - else - new_plans.push_back(&install_action); + remove_specs.erase(it); + rebuilt_plans.push_back(&install_action); } } - already_installed_plans = Util::fmap(action_plan.already_installed, [](auto&& action) { return &action; }); - std::sort(rebuilt_plans.begin(), rebuilt_plans.end(), &InstallPlanAction::compare_by_name); - std::sort(new_plans.begin(), new_plans.end(), &InstallPlanAction::compare_by_name); - std::sort(already_installed_plans.begin(), already_installed_plans.end(), &InstallPlanAction::compare_by_name); - std::sort(excluded.begin(), excluded.end(), &InstallPlanAction::compare_by_name); + Util::sort(rebuilt_plans, &InstallPlanAction::compare_by_name); + Util::sort(new_plans, &InstallPlanAction::compare_by_name); + Util::sort(already_installed_plans, &InstallPlanAction::compare_by_name); + Util::sort(already_installed_head_plans, &InstallPlanAction::compare_by_name); + Util::sort(excluded, &InstallPlanAction::compare_by_name); if (!excluded.empty()) { - format_plan_block(ret.text, msgExcludedPackages, false, excluded, builtin_ports_dir); + format_plan_block(ret.warning_text, msgExcludedPackages, false, excluded, builtin_ports_dir); + } + + if (!already_installed_head_plans.empty()) + { + format_plan_block( + ret.warning_text, msgInstalledPackagesHead, false, already_installed_head_plans, builtin_ports_dir); } if (!already_installed_plans.empty()) { - format_plan_block(ret.text, msgInstalledPackages, false, already_installed_plans, builtin_ports_dir); + format_plan_block(ret.normal_text, msgInstalledPackages, false, already_installed_plans, builtin_ports_dir); } if (!remove_specs.empty()) { - format_plan_block(ret.text, msgPackagesToRemove, remove_specs); + format_plan_block(ret.normal_text, msgPackagesToRemove, remove_specs); } if (!rebuilt_plans.empty()) { - format_plan_block(ret.text, msgPackagesToRebuild, true, rebuilt_plans, builtin_ports_dir); + format_plan_block(ret.normal_text, msgPackagesToRebuild, true, rebuilt_plans, builtin_ports_dir); } if (!new_plans.empty()) { - format_plan_block(ret.text, msgPackagesToInstall, true, new_plans, builtin_ports_dir); + format_plan_block(ret.normal_text, msgPackagesToInstall, true, new_plans, builtin_ports_dir); } if (has_non_user_requested_packages) { - ret.text.append(msgPackagesToModify).append_raw('\n'); + ret.normal_text.append(msgPackagesToModify).append_raw('\n'); } ret.has_removals = !remove_specs.empty() || !rebuilt_plans.empty(); @@ -1314,7 +1349,12 @@ namespace vcpkg FormattedPlan print_plan(const ActionPlan& action_plan, const Path& builtin_ports_dir) { auto formatted = format_plan(action_plan, builtin_ports_dir); - msg::print(formatted.text); + if (!formatted.warning_text.empty()) + { + msg::print(Color::warning, formatted.warning_text); + } + + msg::print(formatted.normal_text); return formatted; } diff --git a/src/vcpkg/spdx.cpp b/src/vcpkg/spdx.cpp index d5c77cee5e..8906599a6f 100644 --- a/src/vcpkg/spdx.cpp +++ b/src/vcpkg/spdx.cpp @@ -14,10 +14,17 @@ static std::string fix_ref_version(StringView ref, StringView version) return Strings::replace_all(ref, "${VERSION}", version); } -static std::string conclude_license(const std::string& license) +static StringView conclude_license(const Optional& maybe_license) { - if (license.empty()) return SpdxNoAssertion.to_string(); - return license; + if (auto license = maybe_license.get()) + { + if (!license->empty()) + { + return *license; + } + } + + return SpdxNoAssertion; } static void append_move_if_exists_and_array(Json::Array& out, Json::Object& obj, StringView property) @@ -82,177 +89,232 @@ static Json::Object make_resource( return obj; } -Json::Object vcpkg::run_resource_heuristics(StringView contents, StringView version_text) +namespace vcpkg { - // These are a sequence of heuristics to enable proof-of-concept extraction of remote resources for SPDX SBOM - // inclusion - size_t n = 0; - Json::Object ret; - auto& packages = ret.insert(JsonIdPackages, Json::Array{}); - auto github = find_cmake_invocation(contents, "vcpkg_from_github"); - if (!github.empty()) - { - auto repo = extract_cmake_invocation_argument(github, CMakeVariableRepo); - auto ref = fix_ref_version(extract_cmake_invocation_argument(github, CMakeVariableRef), version_text); - auto sha = extract_cmake_invocation_argument(github, CMakeVariableSHA512); - - packages.push_back(make_resource(fmt::format("SPDXRef-resource-{}", ++n), - repo, - fmt::format("git+https://github.com/{}@{}", repo, ref), - sha, - {})); - } - auto git = find_cmake_invocation(contents, "vcpkg_from_git"); - if (!git.empty()) - { - auto url = extract_cmake_invocation_argument(github, CMakeVariableUrl); - auto ref = fix_ref_version(extract_cmake_invocation_argument(github, CMakeVariableRef), version_text); - packages.push_back( - make_resource(fmt::format("SPDXRef-resource-{}", ++n), url, fmt::format("git+{}@{}", url, ref), {}, {})); - } - auto distfile = find_cmake_invocation(contents, "vcpkg_download_distfile"); - if (!distfile.empty()) + Json::Object run_resource_heuristics(StringView contents, StringView version_text) { - auto url = extract_cmake_invocation_argument(distfile, CMakeVariableUrls); - auto filename = extract_cmake_invocation_argument(distfile, CMakeVariableFilename); - auto sha = extract_cmake_invocation_argument(distfile, CMakeVariableSHA512); - packages.push_back( - make_resource(fmt::format("SPDXRef-resource-{}", ++n), filename, url.to_string(), sha, filename)); - } - auto sfg = find_cmake_invocation(contents, "vcpkg_from_sourceforge"); - if (!sfg.empty()) - { - auto repo = extract_cmake_invocation_argument(sfg, CMakeVariableRepo); - auto ref = fix_ref_version(extract_cmake_invocation_argument(sfg, CMakeVariableRef), version_text); - auto filename = extract_cmake_invocation_argument(sfg, CMakeVariableFilename); - auto sha = extract_cmake_invocation_argument(sfg, CMakeVariableSHA512); - auto url = fmt::format("https://sourceforge.net/projects/{}/files/{}/{}", repo, ref, filename); - packages.push_back( - make_resource(fmt::format("SPDXRef-resource-{}", ++n), filename, std::move(url), sha, filename)); - } - return ret; -} - -std::string vcpkg::create_spdx_sbom(const InstallPlanAction& action, - View relative_paths, - View hashes, - std::string created_time, - std::string document_namespace, - std::vector&& resource_docs) -{ - Checks::check_exit(VCPKG_LINE_INFO, relative_paths.size() == hashes.size()); - - const auto& scfl = action.source_control_file_and_location.value_or_exit(VCPKG_LINE_INFO); - const auto& cpgh = *scfl.source_control_file->core_paragraph; - StringView abi{SpdxNone}; - if (auto package_abi = action.package_abi().get()) - { - abi = *package_abi; - } - - Json::Object doc; - doc.insert(JsonIdDollarSchema, "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json"); - doc.insert(SpdxVersion, SpdxTwoTwo); - doc.insert(SpdxDataLicense, SpdxCCZero); - doc.insert(SpdxSpdxId, SpdxRefDocument); - doc.insert(SpdxDocumentNamespace, std::move(document_namespace)); - doc.insert(JsonIdName, fmt::format("{}@{} {}", action.spec, cpgh.version, abi)); - { - auto& cinfo = doc.insert(SpdxCreationInfo, Json::Object()); - auto& creators = cinfo.insert(JsonIdCreators, Json::Array()); - creators.push_back(Strings::concat("Tool: vcpkg-", VCPKG_BASE_VERSION_AS_STRING, '-', VCPKG_VERSION_AS_STRING)); - cinfo.insert(JsonIdCreated, std::move(created_time)); - } + // These are a sequence of heuristics to enable proof-of-concept extraction of remote resources for SPDX SBOM + // inclusion + size_t n = 0; + Json::Object ret; + auto& packages = ret.insert(JsonIdPackages, Json::Array{}); + auto github = find_cmake_invocation(contents, "vcpkg_from_github"); + if (!github.empty()) + { + auto repo = extract_cmake_invocation_argument(github, CMakeVariableRepo); + auto ref = fix_ref_version(extract_cmake_invocation_argument(github, CMakeVariableRef), version_text); + auto sha = extract_cmake_invocation_argument(github, CMakeVariableSHA512); - auto& rels = doc.insert(JsonIdRelationships, Json::Array()); - auto& packages = doc.insert(JsonIdPackages, Json::Array()); - { - auto& obj = packages.push_back(Json::Object()); - obj.insert(JsonIdName, action.spec.name()); - obj.insert(SpdxSpdxId, SpdxRefPort); - obj.insert(SpdxVersionInfo, cpgh.version.to_string()); - obj.insert(SpdxDownloadLocation, scfl.spdx_location.empty() ? StringView{SpdxNoAssertion} : scfl.spdx_location); - if (!cpgh.homepage.empty()) + packages.push_back(make_resource(fmt::format("SPDXRef-resource-{}", ++n), + repo, + fmt::format("git+https://github.com/{}@{}", repo, ref), + sha, + {})); + } + auto git = find_cmake_invocation(contents, "vcpkg_from_git"); + if (!git.empty()) { - obj.insert(JsonIdHomepage, cpgh.homepage); + auto url = extract_cmake_invocation_argument(github, CMakeVariableUrl); + auto ref = fix_ref_version(extract_cmake_invocation_argument(github, CMakeVariableRef), version_text); + packages.push_back(make_resource( + fmt::format("SPDXRef-resource-{}", ++n), url, fmt::format("git+{}@{}", url, ref), {}, {})); } - obj.insert(SpdxLicenseConcluded, conclude_license(cpgh.license.value_or(""))); - obj.insert(SpdxLicenseDeclared, SpdxNoAssertion); - obj.insert(SpdxCopyrightText, SpdxNoAssertion); - if (!cpgh.summary.empty()) obj.insert(JsonIdSummary, Strings::join("\n", cpgh.summary)); - if (!cpgh.description.empty()) obj.insert(JsonIdDescription, Strings::join("\n", cpgh.description)); - obj.insert(JsonIdComment, "This is the port (recipe) consumed by vcpkg."); + auto distfile = find_cmake_invocation(contents, "vcpkg_download_distfile"); + if (!distfile.empty()) { - auto& rel = rels.push_back(Json::Object()); - rel.insert(SpdxElementId, SpdxRefPort); - rel.insert(SpdxRelationshipType, SpdxGenerates); - rel.insert(SpdxRelatedSpdxElement, SpdxRefBinary); + auto url = extract_cmake_invocation_argument(distfile, CMakeVariableUrls); + auto filename = extract_cmake_invocation_argument(distfile, CMakeVariableFilename); + auto sha = extract_cmake_invocation_argument(distfile, CMakeVariableSHA512); + packages.push_back( + make_resource(fmt::format("SPDXRef-resource-{}", ++n), filename, url.to_string(), sha, filename)); } - for (size_t i = 0; i < relative_paths.size(); ++i) + auto sfg = find_cmake_invocation(contents, "vcpkg_from_sourceforge"); + if (!sfg.empty()) { - auto& rel = rels.push_back(Json::Object()); - rel.insert(SpdxElementId, SpdxRefPort); - rel.insert(SpdxRelationshipType, SpdxContains); - rel.insert(SpdxRelatedSpdxElement, fmt::format("SPDXRef-file-{}", i)); + auto repo = extract_cmake_invocation_argument(sfg, CMakeVariableRepo); + auto ref = fix_ref_version(extract_cmake_invocation_argument(sfg, CMakeVariableRef), version_text); + auto filename = extract_cmake_invocation_argument(sfg, CMakeVariableFilename); + auto sha = extract_cmake_invocation_argument(sfg, CMakeVariableSHA512); + auto url = fmt::format("https://sourceforge.net/projects/{}/files/{}/{}", repo, ref, filename); + packages.push_back( + make_resource(fmt::format("SPDXRef-resource-{}", ++n), filename, std::move(url), sha, filename)); } + + return ret; } + + std::string create_spdx_sbom(const InstallPlanAction& action, + View relative_paths, + View hashes, + std::string created_time, + std::string document_namespace, + std::vector&& resource_docs) { - auto& obj = packages.push_back(Json::Object()); - obj.insert(JsonIdName, action.spec.to_string()); - obj.insert(SpdxSpdxId, SpdxRefBinary); - obj.insert(SpdxVersionInfo, abi); - obj.insert(SpdxDownloadLocation, SpdxNone); - obj.insert(SpdxLicenseConcluded, conclude_license(cpgh.license.value_or(""))); - obj.insert(SpdxLicenseDeclared, SpdxNoAssertion); - obj.insert(SpdxCopyrightText, SpdxNoAssertion); - obj.insert(JsonIdComment, "This is a binary package built by vcpkg."); + Checks::check_exit(VCPKG_LINE_INFO, relative_paths.size() == hashes.size()); + + const auto& scfl = action.source_control_file_and_location.value_or_exit(VCPKG_LINE_INFO); + const auto& cpgh = *scfl.source_control_file->core_paragraph; + StringView abi{SpdxNone}; + if (auto package_abi = action.package_abi().get()) { - auto& rel = rels.push_back(Json::Object()); - rel.insert(SpdxElementId, SpdxRefBinary); - rel.insert(SpdxRelationshipType, SpdxGeneratedFrom); - rel.insert(SpdxRelatedSpdxElement, SpdxRefPort); + abi = *package_abi; } - } - auto& files = doc.insert(JsonIdFiles, Json::Array()); - { - for (size_t i = 0; i < relative_paths.size(); ++i) + Json::Object doc; + doc.insert(JsonIdDollarSchema, + "https://raw.githubusercontent.com/spdx/spdx-spec/v2.2.1/schemas/spdx-schema.json"); + doc.insert(SpdxVersion, SpdxTwoTwo); + doc.insert(SpdxDataLicense, SpdxCCZero); + doc.insert(SpdxSpdxId, SpdxRefDocument); + doc.insert(SpdxDocumentNamespace, std::move(document_namespace)); + doc.insert(JsonIdName, fmt::format("{}@{} {}", action.spec, cpgh.version, abi)); { - const auto& path = relative_paths[i]; - const auto& hash = hashes[i]; + auto& cinfo = doc.insert(SpdxCreationInfo, Json::Object()); + auto& creators = cinfo.insert(JsonIdCreators, Json::Array()); + creators.push_back( + Strings::concat("Tool: vcpkg-", VCPKG_BASE_VERSION_AS_STRING, '-', VCPKG_VERSION_AS_STRING)); + cinfo.insert(JsonIdCreated, std::move(created_time)); + } - auto& obj = files.push_back(Json::Object()); - obj.insert(SpdxFileName, "./" + path.generic_u8string()); - const auto ref = fmt::format("SPDXRef-file-{}", i); - obj.insert(SpdxSpdxId, ref); - auto& checksum = obj.insert(JsonIdChecksums, Json::Array()); - auto& checksum1 = checksum.push_back(Json::Object()); - checksum1.insert(JsonIdAlgorithm, JsonIdAllCapsSHA256); - checksum1.insert(SpdxChecksumValue, hash); - obj.insert(SpdxLicenseConcluded, SpdxNoAssertion); + auto& rels = doc.insert(JsonIdRelationships, Json::Array()); + auto& packages = doc.insert(JsonIdPackages, Json::Array()); + { + auto& obj = packages.push_back(Json::Object()); + obj.insert(JsonIdName, action.spec.name()); + obj.insert(SpdxSpdxId, SpdxRefPort); + obj.insert(SpdxVersionInfo, cpgh.version.to_string()); + obj.insert(SpdxDownloadLocation, + scfl.spdx_location.empty() ? StringView{SpdxNoAssertion} : scfl.spdx_location); + if (!cpgh.homepage.empty()) + { + obj.insert(JsonIdHomepage, cpgh.homepage); + } + obj.insert(SpdxLicenseConcluded, conclude_license(cpgh.license)); + obj.insert(SpdxLicenseDeclared, SpdxNoAssertion); obj.insert(SpdxCopyrightText, SpdxNoAssertion); + if (!cpgh.summary.empty()) obj.insert(JsonIdSummary, Strings::join("\n", cpgh.summary)); + if (!cpgh.description.empty()) obj.insert(JsonIdDescription, Strings::join("\n", cpgh.description)); + obj.insert(JsonIdComment, "This is the port (recipe) consumed by vcpkg."); { auto& rel = rels.push_back(Json::Object()); - rel.insert(SpdxElementId, ref); - rel.insert(SpdxRelationshipType, SpdxContainedBy); - rel.insert(SpdxRelatedSpdxElement, SpdxRefPort); + rel.insert(SpdxElementId, SpdxRefPort); + rel.insert(SpdxRelationshipType, SpdxGenerates); + rel.insert(SpdxRelatedSpdxElement, SpdxRefBinary); } - if (path == FileVcpkgDotJson) + for (size_t i = 0; i < relative_paths.size(); ++i) { auto& rel = rels.push_back(Json::Object()); - rel.insert(SpdxElementId, ref); - rel.insert(SpdxRelationshipType, SpdxDependencyManifestOf); + rel.insert(SpdxElementId, SpdxRefPort); + rel.insert(SpdxRelationshipType, SpdxContains); + rel.insert(SpdxRelatedSpdxElement, fmt::format("SPDXRef-file-{}", i)); + } + } + { + auto& obj = packages.push_back(Json::Object()); + obj.insert(JsonIdName, action.spec.to_string()); + obj.insert(SpdxSpdxId, SpdxRefBinary); + obj.insert(SpdxVersionInfo, abi); + obj.insert(SpdxDownloadLocation, SpdxNone); + obj.insert(SpdxLicenseConcluded, conclude_license(cpgh.license)); + obj.insert(SpdxLicenseDeclared, SpdxNoAssertion); + obj.insert(SpdxCopyrightText, SpdxNoAssertion); + obj.insert(JsonIdComment, "This is a binary package built by vcpkg."); + { + auto& rel = rels.push_back(Json::Object()); + rel.insert(SpdxElementId, SpdxRefBinary); + rel.insert(SpdxRelationshipType, SpdxGeneratedFrom); rel.insert(SpdxRelatedSpdxElement, SpdxRefPort); } } + + auto& files = doc.insert(JsonIdFiles, Json::Array()); + { + for (size_t i = 0; i < relative_paths.size(); ++i) + { + const auto& path = relative_paths[i]; + const auto& hash = hashes[i]; + + auto& obj = files.push_back(Json::Object()); + obj.insert(SpdxFileName, "./" + path.generic_u8string()); + const auto ref = fmt::format("SPDXRef-file-{}", i); + obj.insert(SpdxSpdxId, ref); + auto& checksum = obj.insert(JsonIdChecksums, Json::Array()); + auto& checksum1 = checksum.push_back(Json::Object()); + checksum1.insert(JsonIdAlgorithm, JsonIdAllCapsSHA256); + checksum1.insert(SpdxChecksumValue, hash); + obj.insert(SpdxLicenseConcluded, SpdxNoAssertion); + obj.insert(SpdxCopyrightText, SpdxNoAssertion); + { + auto& rel = rels.push_back(Json::Object()); + rel.insert(SpdxElementId, ref); + rel.insert(SpdxRelationshipType, SpdxContainedBy); + rel.insert(SpdxRelatedSpdxElement, SpdxRefPort); + } + if (path == FileVcpkgDotJson) + { + auto& rel = rels.push_back(Json::Object()); + rel.insert(SpdxElementId, ref); + rel.insert(SpdxRelationshipType, SpdxDependencyManifestOf); + rel.insert(SpdxRelatedSpdxElement, SpdxRefPort); + } + } + } + + for (auto&& rdoc : resource_docs) + { + append_move_if_exists_and_array(rels, rdoc, JsonIdRelationships); + append_move_if_exists_and_array(files, rdoc, JsonIdFiles); + append_move_if_exists_and_array(packages, rdoc, JsonIdPackages); + } + + return Json::stringify(doc); } - for (auto&& rdoc : resource_docs) + Optional read_spdx_license(StringView text, StringView origin) { - append_move_if_exists_and_array(rels, rdoc, JsonIdRelationships); - append_move_if_exists_and_array(files, rdoc, JsonIdFiles); - append_move_if_exists_and_array(packages, rdoc, JsonIdPackages); - } + // JsonIdPackages[0]/SpdxLicenseConcluded + auto maybe_parsed = Json::parse_object(text, origin); + auto parsed = maybe_parsed.get(); + if (!parsed) + { + return nullopt; + } + + auto maybe_packages_value = parsed->get(JsonIdPackages); + if (!maybe_packages_value) + { + return nullopt; + } + + auto maybe_packages_array = maybe_packages_value->maybe_array(); + if (!maybe_packages_array || maybe_packages_array->size() == 0) + { + return nullopt; + } + + auto maybe_first_package_object = maybe_packages_array->operator[](0).maybe_object(); + if (!maybe_first_package_object) + { + return nullopt; + } + + auto maybe_license_concluded_value = maybe_first_package_object->get(SpdxLicenseConcluded); + if (!maybe_license_concluded_value) + { + return nullopt; + } + + auto maybe_license_concluded = maybe_license_concluded_value->maybe_string(); + if (!maybe_license_concluded) + { + return nullopt; + } - return Json::stringify(doc); + if (maybe_license_concluded->empty() || *maybe_license_concluded == SpdxNoAssertion) + { + return nullopt; + } + + return std::move(*maybe_license_concluded); + } } diff --git a/src/vcpkg/vcpkglib.cpp b/src/vcpkg/vcpkglib.cpp index eda0c1f641..38923d69bd 100644 --- a/src/vcpkg/vcpkglib.cpp +++ b/src/vcpkg/vcpkglib.cpp @@ -10,21 +10,8 @@ namespace vcpkg { - static StatusParagraphs load_current_database(const Filesystem& fs, - const Path& vcpkg_dir_status_file, - const Path& vcpkg_dir_status_file_old) + static StatusParagraphs load_current_database(const ReadOnlyFilesystem& fs, const Path& vcpkg_dir_status_file) { - if (!fs.exists(vcpkg_dir_status_file, IgnoreErrors{})) - { - if (!fs.exists(vcpkg_dir_status_file_old, IgnoreErrors{})) - { - // no status file, use empty db - return StatusParagraphs(); - } - - fs.rename(vcpkg_dir_status_file_old, vcpkg_dir_status_file, VCPKG_LINE_INFO); - } - auto pghs = Paragraphs::get_paragraphs(fs, vcpkg_dir_status_file).value_or_exit(VCPKG_LINE_INFO); std::vector> status_pghs; @@ -37,7 +24,76 @@ namespace vcpkg return StatusParagraphs(std::move(status_pghs)); } - StatusParagraphs database_load_check(const Filesystem& fs, const InstalledPaths& installed) + static std::vector apply_database_updates(const ReadOnlyFilesystem& fs, + StatusParagraphs& current_status_db, + const Path& updates_dir) + { + auto update_files = fs.get_regular_files_non_recursive(updates_dir, VCPKG_LINE_INFO); + Util::sort(update_files); + if (!update_files.empty()) + { + for (auto&& file : update_files) + { + if (file.filename() == FileIncomplete) continue; + + auto pghs = Paragraphs::get_paragraphs(fs, file).value_or_exit(VCPKG_LINE_INFO); + for (auto&& p : pghs) + { + current_status_db.insert(std::make_unique(file, std::move(p))); + } + } + } + + return update_files; + } + + static void apply_database_updates_on_disk(const Filesystem& fs, + const InstalledPaths& installed, + StatusParagraphs& current_status_db) + { + auto update_files = apply_database_updates(fs, current_status_db, installed.vcpkg_dir_updates()); + if (!update_files.empty()) + { + const auto status_file = installed.vcpkg_dir_status_file(); + const auto status_file_new = Path(status_file.parent_path()) / FileStatusNew; + fs.write_contents(status_file_new, Strings::serialize(current_status_db), VCPKG_LINE_INFO); + + fs.rename(status_file_new, status_file, VCPKG_LINE_INFO); + + for (auto&& file : update_files) + { + fs.remove(file, VCPKG_LINE_INFO); + } + } + } + + StatusParagraphs database_load(const ReadOnlyFilesystem& fs, const InstalledPaths& installed) + { + const auto maybe_status_file = installed.vcpkg_dir_status_file(); + const auto status_parent = Path(maybe_status_file.parent_path()); + const auto status_file_old = status_parent / FileStatusOld; + + auto status_file = &maybe_status_file; + + if (!fs.exists(maybe_status_file, IgnoreErrors{})) + { + if (!fs.exists(status_file_old, IgnoreErrors{})) + { + // no status file, use empty db + StatusParagraphs current_status_db; + (void)apply_database_updates(fs, current_status_db, installed.vcpkg_dir_updates()); + return current_status_db; + } + + status_file = &status_file_old; + } + + StatusParagraphs current_status_db = load_current_database(fs, *status_file); + (void)apply_database_updates(fs, current_status_db, installed.vcpkg_dir_updates()); + return current_status_db; + } + + StatusParagraphs database_load_collapse(const Filesystem& fs, const InstalledPaths& installed) { const auto updates_dir = installed.vcpkg_dir_updates(); @@ -48,38 +104,23 @@ namespace vcpkg const auto status_file = installed.vcpkg_dir_status_file(); const auto status_parent = Path(status_file.parent_path()); - const auto status_file_old = status_parent / "status-old"; - const auto status_file_new = status_parent / "status-new"; - - StatusParagraphs current_status_db = load_current_database(fs, status_file, status_file_old); + const auto status_file_old = status_parent / FileStatusOld; - auto update_files = fs.get_regular_files_non_recursive(updates_dir, VCPKG_LINE_INFO); - Util::sort(update_files); - if (update_files.empty()) - { - // updates directory is empty, control file is up-to-date. - return current_status_db; - } - for (auto&& file : update_files) + if (!fs.exists(status_file, IgnoreErrors{})) { - if (file.filename() == "incomplete") continue; - - auto pghs = Paragraphs::get_paragraphs(fs, file).value_or_exit(VCPKG_LINE_INFO); - for (auto&& p : pghs) + if (!fs.exists(status_file_old, IgnoreErrors{})) { - current_status_db.insert(std::make_unique(file, std::move(p))); + // no status file, use empty db + StatusParagraphs current_status_db; + apply_database_updates_on_disk(fs, installed, current_status_db); + return current_status_db; } - } - - fs.write_contents(status_file_new, Strings::serialize(current_status_db), VCPKG_LINE_INFO); - fs.rename(status_file_new, status_file, VCPKG_LINE_INFO); - - for (auto&& file : update_files) - { - fs.remove(file, VCPKG_LINE_INFO); + fs.rename(status_file_old, status_file, VCPKG_LINE_INFO); } + StatusParagraphs current_status_db = load_current_database(fs, status_file); + apply_database_updates_on_disk(fs, installed, current_status_db); return current_status_db; } @@ -90,28 +131,25 @@ namespace vcpkg const auto my_update_id = update_id++; const auto update_path = installed.vcpkg_dir_updates() / fmt::format("{:010}", my_update_id); - fs.write_rename_contents(update_path, "incomplete", Strings::serialize(p), VCPKG_LINE_INFO); + fs.write_rename_contents(update_path, FileIncomplete, Strings::serialize(p), VCPKG_LINE_INFO); } - static void upgrade_to_slash_terminated_sorted_format(const Filesystem& fs, - std::vector* lines, - const Path& listfile_path) + static bool upgrade_to_slash_terminated_sorted_format(std::vector& lines) { - static bool was_tracked = false; + static std::atomic was_tracked = false; - if (lines->empty()) + if (lines.empty()) { - return; + return false; } - if (lines->at(0).back() == '/') + if (lines.front().back() == '/') { - return; // File already in the new format + return false; // File already in the new format } - if (!was_tracked) + if (was_tracked.exchange(true)) { - was_tracked = true; get_global_metrics_collector().track_string(StringMetric::ListFile, "update to new format"); } @@ -119,14 +157,15 @@ namespace vcpkg // (They are not necessarily sorted alphabetically, e.g. libflac) // Therefore we can detect the entries that represent directories by comparing every element with the next one // and checking if the next has a slash immediately after the current one's length - for (size_t i = 0; i < lines->size() - 1; i++) + const size_t end = lines.size() - 1; + for (size_t i = 0; i < end; i++) { - std::string& current_string = lines->at(i); - const std::string& next_string = lines->at(i + 1); - + std::string& current_string = lines[i]; + const std::string& next_string = lines[i + 1]; + // check if the next line is the same as this one with a slash after; that indicates that this one + // represents a directory const size_t potential_slash_char_index = current_string.length(); - // Make sure the index exists first - if (next_string.size() > potential_slash_char_index && next_string.at(potential_slash_char_index) == '/') + if (next_string.size() > potential_slash_char_index && next_string[potential_slash_char_index] == '/') { current_string += '/'; // Mark as a directory } @@ -155,12 +194,8 @@ namespace vcpkg */ // Note that after sorting, the FLAC++/ group will be placed before the FLAC/ group // The new format is lexicographically sorted - std::sort(lines->begin(), lines->end()); - - // Replace the listfile on disk - const auto updated_listfile_path = listfile_path + "_updated"; - fs.write_lines(updated_listfile_path, *lines, VCPKG_LINE_INFO); - fs.rename(updated_listfile_path, listfile_path, VCPKG_LINE_INFO); + Util::sort(lines); + return true; } std::vector get_installed_ports(const StatusParagraphs& status_db) @@ -189,9 +224,10 @@ namespace vcpkg return Util::fmap(ipv_map, [](auto&& p) -> InstalledPackageView { return std::move(p.second); }); } - std::vector get_installed_files(const Filesystem& fs, - const InstalledPaths& installed, - const StatusParagraphs& status_db) + template + static std::vector get_installed_files_impl(const FilesystemLike& fs, + const InstalledPaths& installed, + const StatusParagraphs& status_db) { std::vector installed_files; @@ -206,7 +242,16 @@ namespace vcpkg std::vector installed_files_of_current_pgh = fs.read_lines(listfile_path).value_or_exit(VCPKG_LINE_INFO); Strings::inplace_trim_all_and_remove_whitespace_strings(installed_files_of_current_pgh); - upgrade_to_slash_terminated_sorted_format(fs, &installed_files_of_current_pgh, listfile_path); + if (upgrade_to_slash_terminated_sorted_format(installed_files_of_current_pgh)) + { + if constexpr (AndUpdate) + { + // Replace the listfile on disk + const auto updated_listfile_path = listfile_path + "_updated"; + fs.write_lines(updated_listfile_path, installed_files_of_current_pgh, VCPKG_LINE_INFO); + fs.rename(updated_listfile_path, listfile_path, VCPKG_LINE_INFO); + } + } // Remove the directories Util::erase_remove_if(installed_files_of_current_pgh, @@ -220,6 +265,20 @@ namespace vcpkg return installed_files; } + std::vector get_installed_files(const ReadOnlyFilesystem& fs, + const InstalledPaths& installed, + const StatusParagraphs& status_db) + { + return get_installed_files_impl(fs, installed, status_db); + } + + std::vector get_installed_files_and_upgrade(const Filesystem& fs, + const InstalledPaths& installed, + const StatusParagraphs& status_db) + { + return get_installed_files_impl(fs, installed, status_db); + } + std::string shorten_text(StringView desc, const size_t length) { Checks::check_exit(VCPKG_LINE_INFO, length >= 3);