diff --git a/azure-pipelines/e2e_ports/overlays/absolute-paths/hash.in b/azure-pipelines/e2e_ports/overlays/absolute-paths/hash.in new file mode 100644 index 0000000000..255f6caa01 --- /dev/null +++ b/azure-pipelines/e2e_ports/overlays/absolute-paths/hash.in @@ -0,0 +1,3 @@ +#!/bin/bash + +test="${CURRENT_INSTALLED_DIR}" diff --git a/azure-pipelines/e2e_ports/overlays/absolute-paths/portfile.cmake b/azure-pipelines/e2e_ports/overlays/absolute-paths/portfile.cmake new file mode 100644 index 0000000000..11c8d5be2a --- /dev/null +++ b/azure-pipelines/e2e_ports/overlays/absolute-paths/portfile.cmake @@ -0,0 +1,36 @@ +set(VCPKG_POLICY_EMPTY_INCLUDE_FOLDER enabled) + +if("usage" IN_LIST FEATURES) + file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/usage" "Set TEST=${CURRENT_INSTALLED_DIR} to use the port\n") +endif() +if("header" IN_LIST FEATURES) + configure_file("${CURRENT_PORT_DIR}/source.h.in" "${CURRENT_PACKAGES_DIR}/include/test.h") +endif() +if("header-comment" IN_LIST FEATURES) + configure_file("${CURRENT_PORT_DIR}/source-comment.h.in" "${CURRENT_PACKAGES_DIR}/include/test.h") +endif() +if("python" IN_LIST FEATURES) + file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/test.py" "test = \"${CURRENT_INSTALLED_DIR}\"\n") + message(STATUS "Wtite to ${CURRENT_PACKAGES_DIR}/share/${PORT}/test.py") +endif() +if("python-comment" IN_LIST FEATURES) + file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/test.py" "# test = \"${CURRENT_INSTALLED_DIR}\"\n") +endif() +if("hash" IN_LIST FEATURES) + configure_file("${CURRENT_PORT_DIR}/hash.in" "${CURRENT_PACKAGES_DIR}/share/${PORT}/test") +endif() +if("new-policy" IN_LIST FEATURES) + set(VCPKG_POLICY_SKIP_ABSOLUTE_PATHS_CHECK enabled) +endif() +if("packages" IN_LIST FEATURES) + file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/test.py" "${CURRENT_PACKAGES_DIR}") +endif() +if("buildtrees" IN_LIST FEATURES) + file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/test.py" "${CURRENT_BUILDTREES_DIR}") +endif() +if("native" IN_LIST FEATURES) + cmake_path(NATIVE_PATH CURRENT_INSTALLED_DIR CURRENT_INSTALLED_DIR_NATIVE) + file(WRITE "${CURRENT_PACKAGES_DIR}/share/${PORT}/test.py" "${CURRENT_INSTALLED_DIR_NATIVE}") +endif() + +file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/../../../../LICENSE.txt" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}" RENAME copyright) diff --git a/azure-pipelines/e2e_ports/overlays/absolute-paths/source-comment.h.in b/azure-pipelines/e2e_ports/overlays/absolute-paths/source-comment.h.in new file mode 100644 index 0000000000..9489af8546 --- /dev/null +++ b/azure-pipelines/e2e_ports/overlays/absolute-paths/source-comment.h.in @@ -0,0 +1,6 @@ + +// char * test = "${CURRENT_INSTALLED_DIR}" + +/* +${CURRENT_INSTALLED_DIR} +*/ diff --git a/azure-pipelines/e2e_ports/overlays/absolute-paths/source.h.in b/azure-pipelines/e2e_ports/overlays/absolute-paths/source.h.in new file mode 100644 index 0000000000..42d7ebda69 --- /dev/null +++ b/azure-pipelines/e2e_ports/overlays/absolute-paths/source.h.in @@ -0,0 +1,5 @@ + +char * test2 = R"--(/* + +// ${CURRENT_INSTALLED_DIR} */ +)--"; diff --git a/azure-pipelines/e2e_ports/overlays/absolute-paths/vcpkg.json b/azure-pipelines/e2e_ports/overlays/absolute-paths/vcpkg.json new file mode 100644 index 0000000000..49c4d343d8 --- /dev/null +++ b/azure-pipelines/e2e_ports/overlays/absolute-paths/vcpkg.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", + "name": "absolute-paths", + "version": "1.0.0", + "features": { + "usage": { + "description": "" + }, + "header": { + "description": "" + }, + "header-comment": { + "description": "" + }, + "python": { + "description": "" + }, + "python-comment": { + "description": "" + }, + "hash": { + "description": "" + }, + "new-policy": { + "description": "" + }, + "packages": { + "description": "" + }, + "buildtrees": { + "description": "" + }, + "native": { + "description": "" + } + } +} diff --git a/azure-pipelines/end-to-end-tests-dir/no-absolute-paths.ps1 b/azure-pipelines/end-to-end-tests-dir/no-absolute-paths.ps1 new file mode 100644 index 0000000000..b004a4993b --- /dev/null +++ b/azure-pipelines/end-to-end-tests-dir/no-absolute-paths.ps1 @@ -0,0 +1,41 @@ +. $PSScriptRoot/../end-to-end-tests-prelude.ps1 + +$CurrentTest = "No absolute paths" + +$commonArgs += @("--enforce-port-checks", "--binarysource=clear") + +Run-Vcpkg @commonArgs install "absolute-paths[hash]" +Throw-IfNotFailed +Remove-Item -Recurse -Force $installRoot + +Run-Vcpkg @commonArgs install "absolute-paths[python]" +Throw-IfNotFailed +Remove-Item -Recurse -Force $installRoot + +Run-Vcpkg @commonArgs install "absolute-paths[python-comment]" +Throw-IfFailed +Remove-Item -Recurse -Force $installRoot + +Run-Vcpkg @commonArgs install "absolute-paths[header]" +Throw-IfNotFailed +Remove-Item -Recurse -Force $installRoot + +Run-Vcpkg @commonArgs install "absolute-paths[header-comment]" +Throw-IfFailed +Remove-Item -Recurse -Force $installRoot + +Run-Vcpkg @commonArgs install "absolute-paths[usage]" +Throw-IfNotFailed +Remove-Item -Recurse -Force $installRoot + +Run-Vcpkg @commonArgs install "absolute-paths[usage, new-policy]" +Throw-IfFailed +Remove-Item -Recurse -Force $installRoot + +Run-Vcpkg @commonArgs install "absolute-paths[packages]" +Throw-IfNotFailed +Remove-Item -Recurse -Force $installRoot + +Run-Vcpkg @commonArgs install "absolute-paths[native]" +Throw-IfNotFailed +Remove-Item -Recurse -Force $installRoot diff --git a/include/vcpkg/base/messages.h b/include/vcpkg/base/messages.h index 370e5b0b4e..ce80a3dcfc 100644 --- a/include/vcpkg/base/messages.h +++ b/include/vcpkg/base/messages.h @@ -1338,6 +1338,13 @@ namespace vcpkg "One or more {vendor} credential providers failed to authenticate. See '{url}' for more details " "on how to provide credentials."); DECLARE_MESSAGE(FeedbackAppreciated, (), "", "Thank you for your feedback!"); + DECLARE_MESSAGE( + FilesContainAbsolutePath1, + (), + "This message is printed before a list of found absolute paths, followed by FilesContainAbsolutePath2, " + "followed by a list of found files.", + "There should be no absolute paths, such as the following, in an installed package:"); + DECLARE_MESSAGE(FilesContainAbsolutePath2, (), "", "Absolute paths were found in the following files:"); DECLARE_MESSAGE(FetchingBaselineInfo, (msg::package_name), "", diff --git a/include/vcpkg/base/strings.h b/include/vcpkg/base/strings.h index 5208f9c51d..fbfb55ab45 100644 --- a/include/vcpkg/base/strings.h +++ b/include/vcpkg/base/strings.h @@ -12,6 +12,8 @@ #include #include +#include "vcpkg/base/fwd/span.h" + namespace vcpkg::Strings::details { // first looks up to_string on `T` using ADL; then, if that isn't found, @@ -218,6 +220,12 @@ namespace vcpkg::Strings Optional find_at_most_one_enclosed(StringView input, StringView left_tag, StringView right_tag); + bool contains_any_ignoring_c_comments(const std::string& source, View to_find); + + bool contains_any_ignoring_hash_comments(StringView source, View to_find); + + bool contains_any(StringView source, View to_find); + bool equals(StringView a, StringView b); template diff --git a/include/vcpkg/base/util.h b/include/vcpkg/base/util.h index 43b5a8d06e..6a86dcf646 100644 --- a/include/vcpkg/base/util.h +++ b/include/vcpkg/base/util.h @@ -266,6 +266,13 @@ namespace vcpkg::Util return std::find(begin(cont), end(cont), v); } + template + bool contains(const Range& r, const T& el) + { + using std::end; + return Util::find(r, el) != end(r); + } + template auto find_if(Container&& cont, Pred pred) { diff --git a/include/vcpkg/fwd/build.h b/include/vcpkg/fwd/build.h index 0acf62c41d..c65ca91036 100644 --- a/include/vcpkg/fwd/build.h +++ b/include/vcpkg/fwd/build.h @@ -106,6 +106,7 @@ namespace vcpkg SKIP_DUMPBIN_CHECKS, SKIP_ARCHITECTURE_CHECK, CMAKE_HELPER_PORT, + SKIP_ABSOLUTE_PATHS_CHECK, // Must be last COUNT, }; diff --git a/locales/messages.json b/locales/messages.json index 2e9c9aefd9..8700a8a90a 100644 --- a/locales/messages.json +++ b/locales/messages.json @@ -540,6 +540,9 @@ "FileSeekFailed": "Failed to seek to position {byte_offset} in {path}.", "_FileSeekFailed.comment": "An example of {path} is /foo/bar. An example of {byte_offset} is 42.", "FileSystemOperationFailed": "Filesystem operation failed:", + "FilesContainAbsolutePath1": "There should be no absolute paths, such as the following, in an installed package:", + "_FilesContainAbsolutePath1.comment": "This message is printed before a list of found absolute paths, followed by FilesContainAbsolutePath2, followed by a list of found files.", + "FilesContainAbsolutePath2": "Absolute paths were found in the following files:", "FilesExported": "Files exported at: {path}", "_FilesExported.comment": "An example of {path} is /foo/bar.", "FishCompletion": "vcpkg fish completion is already added at \"{path}\".", diff --git a/src/vcpkg-test/strings.cpp b/src/vcpkg-test/strings.cpp index 02f68348e2..ea0b75fb8e 100644 --- a/src/vcpkg-test/strings.cpp +++ b/src/vcpkg-test/strings.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -55,6 +56,75 @@ TEST_CASE ("find_first_of", "[strings]") REQUIRE(find_first_of("abcdefg", "gb") == std::string("bcdefg")); } +TEST_CASE ("contains_any_ignoring_c_comments", "[strings]") +{ + using vcpkg::Strings::contains_any_ignoring_c_comments; + vcpkg::StringView to_find[] = {"abc", "wer"}; + REQUIRE(contains_any_ignoring_c_comments(R"(abc)", to_find)); + REQUIRE(contains_any_ignoring_c_comments(R"("abc")", to_find)); + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"("" //abc)", to_find)); + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"(/*abc*/ "")", to_find)); + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"(/**abc*/ "")", to_find)); + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"(/**abc**/ "")", to_find)); + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"(/*abc)", to_find)); + // note that the line end is escaped making the single line comment include the abc + REQUIRE_FALSE(contains_any_ignoring_c_comments("// test \\\nabc", to_find)); + // note that the comment start is in a string literal so it isn't a comment + REQUIRE(contains_any_ignoring_c_comments("\"//\" test abc", to_find)); + // note that the comment is in a raw string literal so it isn't a comment + REQUIRE(contains_any_ignoring_c_comments(R"-(R"( // abc )")-", to_find)); + // found after the raw string literal + REQUIRE(contains_any_ignoring_c_comments(R"-(R"( // )" abc)-", to_find)); + // comment after the raw string literal + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"-(R"( // )" // abc)-", to_find)); + // the above, but with a d_char_sequence for the raw literal + REQUIRE(contains_any_ignoring_c_comments(R"-(R"hello( // abc )hello")-", to_find)); + REQUIRE(contains_any_ignoring_c_comments(R"-(R"hello( // )hello" abc)-", to_find)); + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"-(R"hello( // )hello" // abc)-", to_find)); + // the above, but with a d_char_sequence that is a needle + REQUIRE(contains_any_ignoring_c_comments(R"-(R"abc( // abc )abc")-", to_find)); + REQUIRE(contains_any_ignoring_c_comments(R"-(R"abc( // )abc" abc)-", to_find)); + REQUIRE(contains_any_ignoring_c_comments(R"-(R"abc( // )abc" // abc)-", to_find)); + // raw literal termination edge cases + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"-(R")-", to_find)); // ends input + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"-(R"h)-", to_find)); // ends input d_char + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"-(R"()-", to_find)); // ends input paren + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"-(R"h()-", to_find)); // ends input paren d_char + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"-(R"())-", to_find)); // ends input close paren + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"-(R"()")-", to_find)); // ends input exactly + // raw literal termination edge cases (success) + REQUIRE(contains_any_ignoring_c_comments(R"-(abcR")-", to_find)); // ends input + REQUIRE(contains_any_ignoring_c_comments(R"-(abcR"h)-", to_find)); // ends input d_char + REQUIRE(contains_any_ignoring_c_comments(R"-(abcR"()-", to_find)); // ends input paren + REQUIRE(contains_any_ignoring_c_comments(R"-(abcR"h()-", to_find)); // ends input paren d_char + REQUIRE(contains_any_ignoring_c_comments(R"-(abcR"())-", to_find)); // ends input close paren + REQUIRE(contains_any_ignoring_c_comments(R"-(abcR"()")-", to_find)); // ends input exactly + + REQUIRE(contains_any_ignoring_c_comments(R"-(R"()"abc)-", to_find)); + + REQUIRE(contains_any_ignoring_c_comments(R"-(R"hello( hello" // abc )")-", to_find)); + REQUIRE(contains_any_ignoring_c_comments(R"(R"-( // abc )-")", to_find)); + REQUIRE_FALSE(contains_any_ignoring_c_comments(R"(R"-( // hello )-" // abc)", to_find)); + REQUIRE(contains_any_ignoring_c_comments(R"(R"-( /* abc */ )-")", to_find)); + REQUIRE(contains_any_ignoring_c_comments(R"(R"-()- /* abc */ )-")", to_find)); + REQUIRE(contains_any_ignoring_c_comments(R"(qwer )", to_find)); + REQUIRE(contains_any_ignoring_c_comments("\"a\" \"g\" // er \n abc)", to_find)); +} + +TEST_CASE ("contains_any_ignoring_hash_comments", "[strings]") +{ + using vcpkg::Strings::contains_any_ignoring_hash_comments; + vcpkg::StringView to_find[] = {"abc", "wer"}; + REQUIRE(contains_any_ignoring_hash_comments("abc", to_find)); + REQUIRE(contains_any_ignoring_hash_comments("wer", to_find)); + REQUIRE(contains_any_ignoring_hash_comments("wer # test", to_find)); + REQUIRE(contains_any_ignoring_hash_comments("\n wer # \n test", to_find)); + REQUIRE_FALSE(contains_any_ignoring_hash_comments("# wer", to_find)); + REQUIRE_FALSE(contains_any_ignoring_hash_comments("\n# wer", to_find)); + REQUIRE_FALSE(contains_any_ignoring_hash_comments("\n # wer\n", to_find)); + REQUIRE_FALSE(contains_any_ignoring_hash_comments("\n test # wer", to_find)); +} + TEST_CASE ("edit distance", "[strings]") { using vcpkg::Strings::byte_edit_distance; diff --git a/src/vcpkg/base/messages.cpp b/src/vcpkg/base/messages.cpp index 4dd23d05ed..59f6419e81 100644 --- a/src/vcpkg/base/messages.cpp +++ b/src/vcpkg/base/messages.cpp @@ -659,13 +659,15 @@ namespace vcpkg REGISTER_MESSAGE(FeedbackAppreciated); REGISTER_MESSAGE(FetchingBaselineInfo); REGISTER_MESSAGE(FetchingRegistryInfo); - REGISTER_MESSAGE(FishCompletion); REGISTER_MESSAGE(FloatingPointConstTooBig); REGISTER_MESSAGE(FileNotFound); REGISTER_MESSAGE(FileReadFailed); REGISTER_MESSAGE(FileSeekFailed); REGISTER_MESSAGE(FilesExported); REGISTER_MESSAGE(FileSystemOperationFailed); + REGISTER_MESSAGE(FishCompletion); + REGISTER_MESSAGE(FilesContainAbsolutePath1); + REGISTER_MESSAGE(FilesContainAbsolutePath2); REGISTER_MESSAGE(FollowingPackagesMissingControl); REGISTER_MESSAGE(FollowingPackagesNotInstalled); REGISTER_MESSAGE(FollowingPackagesUpgraded); diff --git a/src/vcpkg/base/strings.cpp b/src/vcpkg/base/strings.cpp index 846a29e7b6..296bff386f 100644 --- a/src/vcpkg/base/strings.cpp +++ b/src/vcpkg/base/strings.cpp @@ -382,6 +382,102 @@ Optional Strings::find_at_most_one_enclosed(StringView input, String return result.front(); } +bool vcpkg::Strings::contains_any_ignoring_c_comments(const std::string& source, View to_find) +{ + std::string::size_type offset = 0; + std::string::size_type no_comment_offset = 0; + while (offset != std::string::npos) + { + no_comment_offset = std::max(offset, no_comment_offset); + auto start = source.find_first_of("/\"", no_comment_offset); + if (start == std::string::npos || start + 1 == source.size() || no_comment_offset == std::string::npos) + { + return Strings::contains_any(StringView(source).substr(offset), to_find); + } + + if (source[start] == '/') + { + if (source[start + 1] == '/' || source[start + 1] == '*') + { + if (Strings::contains_any(StringView(source).substr(offset, start - offset), to_find)) + { + return true; + } + if (source[start + 1] == '/') + { + offset = source.find_first_of('\n', start); + while (offset != std::string::npos && source[offset - 1] == '\\') + offset = source.find_first_of('\n', offset + 1); + if (offset != std::string::npos) ++offset; + continue; + } + offset = source.find_first_of('/', start + 1); + while (offset != std::string::npos && source[offset - 1] != '*') + offset = source.find_first_of('/', offset + 1); + if (offset != std::string::npos) ++offset; + continue; + } + } + else if (source[start] == '\"') + { + if (start > 0 && source[start - 1] == 'R') // raw string literals + { + auto end = source.find_first_of('(', start); + if (end == std::string::npos) + { + // invalid c++, but allowed: auto test = 'R"' + no_comment_offset = start + 1; + continue; + } + auto d_char_sequence = ')' + source.substr(start + 1, end - start - 1); + d_char_sequence.push_back('\"'); + no_comment_offset = source.find(d_char_sequence, end); + if (no_comment_offset != std::string::npos) no_comment_offset += d_char_sequence.size(); + continue; + } + no_comment_offset = source.find_first_of('"', start + 1); + while (no_comment_offset != std::string::npos && source[no_comment_offset - 1] == '\\') + no_comment_offset = source.find_first_of('"', no_comment_offset + 1); + if (no_comment_offset != std::string::npos) ++no_comment_offset; + continue; + } + no_comment_offset = start + 1; + } + return false; +} + +bool Strings::contains_any_ignoring_hash_comments(StringView source, View to_find) +{ + auto first = source.data(); + auto block_start = first; + const auto last = first + source.size(); + for (; first != last; ++first) + { + if (*first == '#') + { + if (Strings::contains_any(StringView{block_start, first}, to_find)) + { + return true; + } + + first = std::find(first, last, '\n'); // skip comment + if (first == last) + { + return false; + } + + block_start = first; + } + } + + return Strings::contains_any(StringView{block_start, last}, to_find); +} + +bool Strings::contains_any(StringView source, View to_find) +{ + return Util::any_of(to_find, [=](StringView s) { return Strings::contains(source, s); }); +} + bool Strings::equals(StringView a, StringView b) { if (a.size() != b.size()) return false; diff --git a/src/vcpkg/build.cpp b/src/vcpkg/build.cpp index 87bf0f152a..a921833b86 100644 --- a/src/vcpkg/build.cpp +++ b/src/vcpkg/build.cpp @@ -219,6 +219,7 @@ namespace vcpkg static const std::string NAME_SKIP_DUMPBIN_CHECKS = "PolicySkipDumpbinChecks"; static const std::string NAME_SKIP_ARCHITECTURE_CHECK = "PolicySkipArchitectureCheck"; static const std::string NAME_CMAKE_HELPER_PORT = "PolicyCmakeHelperPort"; + static const std::string NAME_SKIP_ABSOLUTE_PATHS_CHECK = "PolicySkipAbsolutePathsCheck"; static std::remove_const_t generate_all_policies() { @@ -249,6 +250,7 @@ namespace vcpkg case BuildPolicy::SKIP_DUMPBIN_CHECKS: return NAME_SKIP_DUMPBIN_CHECKS; case BuildPolicy::SKIP_ARCHITECTURE_CHECK: return NAME_SKIP_ARCHITECTURE_CHECK; case BuildPolicy::CMAKE_HELPER_PORT: return NAME_CMAKE_HELPER_PORT; + case BuildPolicy::SKIP_ABSOLUTE_PATHS_CHECK: return NAME_SKIP_ABSOLUTE_PATHS_CHECK; default: Checks::unreachable(VCPKG_LINE_INFO); } } @@ -269,6 +271,7 @@ namespace vcpkg case BuildPolicy::SKIP_DUMPBIN_CHECKS: return "VCPKG_POLICY_SKIP_DUMPBIN_CHECKS"; case BuildPolicy::SKIP_ARCHITECTURE_CHECK: return "VCPKG_POLICY_SKIP_ARCHITECTURE_CHECK"; case BuildPolicy::CMAKE_HELPER_PORT: return "VCPKG_POLICY_CMAKE_HELPER_PORT"; + case BuildPolicy::SKIP_ABSOLUTE_PATHS_CHECK: return "VCPKG_POLICY_SKIP_ABSOLUTE_PATHS_CHECK"; default: Checks::unreachable(VCPKG_LINE_INFO); } } diff --git a/src/vcpkg/postbuildlint.cpp b/src/vcpkg/postbuildlint.cpp index 13a017d2d2..67bbbefbb6 100644 --- a/src/vcpkg/postbuildlint.cpp +++ b/src/vcpkg/postbuildlint.cpp @@ -1,10 +1,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include @@ -972,6 +974,98 @@ namespace vcpkg::PostBuildLint return LintStatus::SUCCESS; } + static bool file_contains_absolute_paths(const Filesystem& fs, + const Path& file, + const std::vector stringview_paths) + { + const auto extension = file.extension(); + if (extension == ".h" || extension == ".hpp" || extension == ".hxx") + { + return Strings::contains_any_ignoring_c_comments(fs.read_contents(file, IgnoreErrors{}), stringview_paths); + } + + if (extension == ".cfg" || extension == ".ini" || file.filename() == "usage") + { + const auto contents = fs.read_contents(file, IgnoreErrors{}); + return Strings::contains_any(contents, stringview_paths); + } + + if (extension == ".py" || extension == ".sh" || extension == ".cmake" || extension == ".pc" || + extension == ".conf") + { + const auto contents = fs.read_contents(file, IgnoreErrors{}); + return Strings::contains_any_ignoring_hash_comments(contents, stringview_paths); + } + + if (extension.empty()) + { + std::error_code ec; + ReadFilePointer read_file(file, ec); + if (ec) return false; + char buffer[5]; + if (read_file.read(buffer, 1, sizeof(buffer)) < sizeof(buffer)) return false; + if (Strings::starts_with(StringView(buffer, sizeof(buffer)), "#!") || + Strings::starts_with(StringView(buffer, sizeof(buffer)), "\xEF\xBB\xBF#!") /* ignore byte-order mark */) + { + const auto contents = fs.read_contents(file, IgnoreErrors{}); + return Strings::contains_any_ignoring_hash_comments(contents, stringview_paths); + } + return false; + } + return false; + } + + static LintStatus check_no_absolute_paths_in(const Filesystem& fs, const Path& dir, Span absolute_paths) + { + std::vector string_paths; + for (const auto& path : absolute_paths) + { +#if defined(_WIN32) + // As supplied, all /s, and all \s + string_paths.push_back(path.native()); + auto path_preferred = path; + path_preferred.make_preferred(); + string_paths.push_back(path_preferred.native()); + string_paths.push_back(path.generic_u8string()); +#else + string_paths.push_back(path.native()); +#endif + } + + Util::sort_unique_erase(string_paths); + + const auto stringview_paths = Util::fmap(string_paths, [](std::string& s) { return StringView(s); }); + + std::vector failing_files; + for (auto&& file : fs.get_regular_files_recursive(dir, IgnoreErrors{})) + { + if (file_contains_absolute_paths(fs, file, stringview_paths)) + { + failing_files.push_back(file); + } + } + + if (failing_files.empty()) + { + return LintStatus::SUCCESS; + } + + auto error_message = msg::format(msgFilesContainAbsolutePath1); + for (auto&& absolute_path : absolute_paths) + { + error_message.append_raw('\n').append_indent().append_raw(absolute_path); + } + + error_message.append_raw('\n').append(msgFilesContainAbsolutePath2); + for (auto&& failure : failing_files) + { + error_message.append_raw('\n').append_indent().append_raw(failure); + } + + msg::println_warning(error_message); + return LintStatus::PROBLEM_DETECTED; + } + static void operator+=(size_t& left, const LintStatus& right) { left += static_cast(right); } static size_t perform_all_checks_and_return_error_count(const PackageSpec& spec, @@ -1100,6 +1194,11 @@ namespace vcpkg::PostBuildLint error_count += check_no_files_in_dir(fs, package_dir); error_count += check_no_files_in_dir(fs, package_dir / "debug"); error_count += check_pkgconfig_dir_only_in_lib_dir(fs, package_dir); + if (!build_info.policies.is_enabled(BuildPolicy::SKIP_ABSOLUTE_PATHS_CHECK)) + { + error_count += check_no_absolute_paths_in( + fs, package_dir, std::vector{package_dir, paths.installed().root(), paths.build_dir(spec)}); + } return error_count; }