diff --git a/src/vcpkg-test/platform-expression.cpp b/src/vcpkg-test/platform-expression.cpp index 635fe517df..53e37c44d1 100644 --- a/src/vcpkg-test/platform-expression.cpp +++ b/src/vcpkg-test/platform-expression.cpp @@ -137,6 +137,42 @@ TEST_CASE ("platform-expression-not", "[platform-expression]") CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}})); } +TEST_CASE ("platform-expression-not-alternate", "[platform-expression]") +{ + auto m_expr = parse_expr("not windows"); + REQUIRE(m_expr); + auto& expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}})); + + m_expr = parse_expr("not windows & not arm & not x86"); + REQUIRE(m_expr); + expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + CHECK_FALSE(expr.evaluate({ + {"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, + {"VCPKG_TARGET_ARCHITECTURE", "arm"}, + })); + + m_expr = parse_expr("not windows and !arm & not x86"); + REQUIRE(m_expr); + expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + CHECK_FALSE(expr.evaluate({ + {"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, + {"VCPKG_TARGET_ARCHITECTURE", "arm"}, + })); +} + TEST_CASE ("platform-expression-and", "[platform-expression]") { auto m_expr = parse_expr("!windows & !arm"); @@ -152,6 +188,48 @@ TEST_CASE ("platform-expression-and", "[platform-expression]") })); } +TEST_CASE ("platform-expression-and-alternate", "[platform-expression]") +{ + auto m_expr = parse_expr("!windows and !arm"); + REQUIRE(m_expr); + auto& expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + CHECK_FALSE(expr.evaluate({ + {"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, + {"VCPKG_TARGET_ARCHITECTURE", "arm"}, + })); +} + +TEST_CASE ("platform-expression-and-multiple", "[platform-expression]") +{ + auto m_expr = parse_expr("!windows & !arm & !x86"); + REQUIRE(m_expr); + auto& expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + CHECK_FALSE(expr.evaluate({ + {"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, + {"VCPKG_TARGET_ARCHITECTURE", "arm"}, + })); + + m_expr = parse_expr("!windows and !arm and !x86"); + REQUIRE(m_expr); + expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + CHECK_FALSE(expr.evaluate({ + {"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, + {"VCPKG_TARGET_ARCHITECTURE", "arm"}, + })); +} + TEST_CASE ("platform-expression-or", "[platform-expression]") { auto m_expr = parse_expr("!windows | arm"); @@ -163,6 +241,130 @@ TEST_CASE ("platform-expression-or", "[platform-expression]") CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); } +TEST_CASE ("platform-expression-or-alternate", "[platform-expression]") +{ + auto m_expr = parse_expr("!windows , arm"); + REQUIRE(m_expr); + auto& expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); +} + +TEST_CASE ("platform-expression-or-multiple", "[platform-expression]") +{ + auto m_expr = parse_expr("!windows | linux | arm"); + REQUIRE(m_expr); + auto& expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + + m_expr = parse_expr("!windows , linux , arm"); + REQUIRE(m_expr); + expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); +} + +TEST_CASE ("platform-expression-mixed-with-parens", "[platform-expression]") +{ + auto m_expr = parse_expr("(x64 | arm64) & (linux | osx | windows)"); + REQUIRE(m_expr); + auto& expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_TARGET_ARCHITECTURE", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); +} + +TEST_CASE ("platform-expression-low-precedence-or", "[platform-expression]") +{ + auto m_expr = parse_expr("(x64 & windows) , (linux & arm)"); + REQUIRE(m_expr); + auto& expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_TARGET_ARCHITECTURE", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "x86"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x86"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); + + m_expr = parse_expr("x64 & windows , linux & arm"); + REQUIRE(m_expr); + expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_TARGET_ARCHITECTURE", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "x86"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x86"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); +} + +TEST_CASE ("mixing &/'and' and , is allowed", "[platform-expression]") +{ + auto m_expr = parse_expr("windows & x86 , linux and x64 , arm64 & osx"); + CHECK(m_expr); + auto& expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_TARGET_ARCHITECTURE", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "x86"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}, {"VCPKG_TARGET_ARCHITECTURE", "x86"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x86"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); + + m_expr = parse_expr("windows , !arm and linux & (x86 | x64)"); + CHECK(m_expr); + expr = *m_expr.get(); + + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_TARGET_ARCHITECTURE", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x86"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); +} + TEST_CASE ("weird platform-expressions whitespace", "[platform-expression]") { auto m_expr = parse_expr(" ! \t windows \n| arm \r"); @@ -174,10 +376,194 @@ TEST_CASE ("weird platform-expressions whitespace", "[platform-expression]") CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); } -TEST_CASE ("no mixing &, | in platform expressions", "[platform-expression]") +TEST_CASE ("platform-expressions without whitespace", "[platform-expression]") +{ + auto m_expr = parse_expr("!windows|linux|arm"); + REQUIRE(m_expr); + auto& expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + + m_expr = parse_expr("!windows&!arm&!x86"); + REQUIRE(m_expr); + expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + CHECK_FALSE(expr.evaluate({ + {"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, + {"VCPKG_TARGET_ARCHITECTURE", "arm"}, + })); + + m_expr = parse_expr("windows,!arm&linux&(x86|x64)"); + REQUIRE(m_expr); + expr = *m_expr.get(); + + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_TARGET_ARCHITECTURE", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x86"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); +} + +TEST_CASE ("operator keywords in identifiers", "[platform-expression]") +{ + // Operator keywords ("and","not") require a break to separate them from identifiers + // In these cases, strings containing an operator keyword parse as an identifier, not as a unary/binary expression + auto m_expr = parse_expr("!windowsandandroid"); + CHECK(m_expr); + + m_expr = parse_expr("notwindows"); + CHECK(m_expr); +} + +TEST_CASE ("operator keywords without whitepace", "[platform-expression]") +{ + // Operator keywords ("and","not") require a break to separate them from identifiers + // A break could be whitespace or a grouped expression (e.g., '(A&B)'). + auto m_expr = parse_expr("(!windows)and(!arm)and(!x86)"); + REQUIRE(m_expr); + auto& expr = *m_expr.get(); + + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + CHECK_FALSE(expr.evaluate({ + {"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, + {"VCPKG_TARGET_ARCHITECTURE", "arm"}, + })); + + m_expr = parse_expr("windows , (!arm )and( linux)and( (x86 | x64) )"); + CHECK(m_expr); + expr = *m_expr.get(); + + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_TARGET_ARCHITECTURE", ""}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x86"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "arm"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}, {"VCPKG_TARGET_ARCHITECTURE", "arm64"}})); + + m_expr = parse_expr("not( !windows& not(x64) )"); + REQUIRE(m_expr); + expr = *m_expr.get(); + + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", ""}, {"VCPKG_TARGET_ARCHITECTURE", "x86"}})); + CHECK(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Darwin"}, {"VCPKG_TARGET_ARCHITECTURE", "x64"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_CMAKE_SYSTEM_NAME", "Linux"}})); + CHECK_FALSE(expr.evaluate({{"VCPKG_TARGET_ARCHITECTURE", "x86"}})); +} + +TEST_CASE ("invalid logic expression, unexpected character", "[platform-expression]") +{ + auto m_expr = parse_expr("windows arm"); + CHECK_FALSE(m_expr); +} + +TEST_CASE ("invalid logic expression, use '|' instead of 'or'", "[platform-expression]") +{ + auto m_expr = parse_expr("windows or arm"); + CHECK_FALSE(m_expr); +} + +TEST_CASE ("unexpected character or identifier in logic expression", "[platform-expression]") +{ + auto m_expr = parse_expr("windows aND arm"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("windows a&d arm"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("windows oR arm"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("windows o|r arm"); + CHECK_FALSE(m_expr); +} + +TEST_CASE ("unexpected identifier in logic expression", "[platform-expression]") +{ + auto m_expr = parse_expr("windows amd arm"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("windows andsynonym arm"); + CHECK_FALSE(m_expr); +} + +TEST_CASE ("missing closing )", "[platform-expression]") +{ + auto m_expr = parse_expr("(windows & arm | linux"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("( (windows & arm) | (osx & arm64) | linux"); + CHECK_FALSE(m_expr); +} + +TEST_CASE ("missing or invalid identifier", "[platform-expression]") +{ + auto m_expr = parse_expr("!"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("w!ndows"); + CHECK_FALSE(m_expr); +} + +TEST_CASE ("mixing & and | is not allowed", "[platform-expression]") { auto m_expr = parse_expr("windows & arm | linux"); CHECK_FALSE(m_expr); m_expr = parse_expr("windows | !arm & linux"); CHECK_FALSE(m_expr); } + +TEST_CASE ("invalid expression, no binary operator", "[platform-expression]") +{ + auto m_expr = parse_expr("windows linux"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("windows x64"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("!windows x86"); + CHECK_FALSE(m_expr); +} + +TEST_CASE ("invalid expression, missing binary operand", "[platform-expression]") +{ + auto m_expr = parse_expr("windows & "); + CHECK_FALSE(m_expr); + m_expr = parse_expr(" | arm"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("windows & !arm & "); + CHECK_FALSE(m_expr); +} + +TEST_CASE ("invalid identifier", "[platform-expression]") +{ + auto m_expr = parse_expr("windows & x^$"); + CHECK_FALSE(m_expr); +} + +TEST_CASE ("invalid alternate expressions", "[platform-expression]") +{ + auto m_expr = parse_expr("windows an%d arm"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("windows aNd arm"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("windows andMORE arm"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("windows and+ arm"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("windows and& arm"); + CHECK_FALSE(m_expr); + + m_expr = parse_expr("notANY windows"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("not! windows"); + CHECK_FALSE(m_expr); + m_expr = parse_expr("notx64 windows"); + CHECK_FALSE(m_expr); +} diff --git a/src/vcpkg/platform-expression.cpp b/src/vcpkg/platform-expression.cpp index b9af8f4a0c..f7ae09b440 100644 --- a/src/vcpkg/platform-expression.cpp +++ b/src/vcpkg/platform-expression.cpp @@ -77,7 +77,10 @@ namespace vcpkg::PlatformExpression identifier, op_not, op_and, - op_or + op_or, + op_list, + op_empty, + op_invalid }; struct ExprImpl @@ -139,34 +142,115 @@ namespace vcpkg::PlatformExpression // platform-expression = // | platform-expression-not // | platform-expression-and - // | platform-expression-or ; + // | platform-expression-or std::unique_ptr expr() { // this is the common prefix of all the variants // platform-expression-not, auto result = expr_not(); - switch (cur()) + // the first expression must be followed by a logical operator (or nothing) + auto oper = expr_operator(); + switch (oper) + { + case ExprKind::op_and: + // { "&", optional-whitespace, platform-expression-not } + // { "and", platform-expression-binary-keyword-second-operand } + return expr_binary( + std::make_unique(oper, std::move(result))); + + case ExprKind::op_or: + // { "|", optional-whitespace, platform-expression-not } + return expr_binary( + std::make_unique(oper, std::move(result))); + + case ExprKind::op_list: + // { ",", optional-whitespace, platform-expression } + return expr_binary( + std::make_unique(oper, std::move(result))); + + case ExprKind::op_empty: return result; + + default: + // op_identifier and op_invalid both indicate a syntax error, which should have + // already been flagged by expr_operator. + return result; + } + } + + ExprKind expr_operator() + { + auto oper = cur(); + + // Support chains of the vcpkg operators (`&`, `|`) to avoid breaking backwards compatibility + switch (oper) + { + case '|': + case '&': + do + { + next(); + } while (allow_multiple_binary_operators() && cur() == oper); + break; + } + + switch (oper) { case '|': { // { "|", optional-whitespace, platform-expression-not } - return expr_binary<'|', '&'>(std::make_unique(ExprKind::op_or, std::move(result))); + return ExprKind::op_or; } case '&': { // { "&", optional-whitespace, platform-expression-not } - return expr_binary<'&', '|'>(std::make_unique(ExprKind::op_and, std::move(result))); + return ExprKind::op_and; + } + case ',': + { + // { ",", optional-whitespace, platform-expression-not } + // "," is a near-synonym of "|", with the differences that it can be combined with "&"/"and", + // but has lower precedence + next(); + return ExprKind::op_list; } - default: return result; + case 'a': + case 'o': + { + // { "and", optional-whitespace, platform-expression-not } + // { "or", platform-expression-binary-keyword-second-operand } } + // "and" is a synonym of "&", "or" is reserved (but not yet supported) as a synonym of "|" + std::string name = match_zero_or_more(is_identifier_char).to_string(); + Checks::check_exit(VCPKG_LINE_INFO, !name.empty()); + + if (name == "and") + { + return ExprKind::op_and; + } + else if (name == "or") + { + add_error("invalid logic expression, use '|' instead of 'or'"); + return ExprKind::op_invalid; + } + + // Invalid alphanumeric strings or strings other than "and" are errors. + add_error("unexpected character or identifier in logic expression"); + return ExprKind::op_invalid; + } + default: + // Perhaps this should be an error, but in the previous implementation, this + // was a do-nothing case, so let's maintain that behavior. + return ExprKind::op_empty; } } // platform-expression-simple = // | platform-expression-identifier - // | "(", optional-whitespace, platform-expression, ")", optional-whitespace ; + // | platform-expression-grouped ; std::unique_ptr expr_simple() { + // platform-expression-grouped = + // | "(", optional-whitespace, platform-expression, ")", optional-whitespace ; if (cur() == '(') { // "(", @@ -200,7 +284,7 @@ namespace vcpkg::PlatformExpression if (name.empty()) { - add_error("unexpected character in logic expression"); + add_error("missing or invalid identifier"); } // optional-whitespace @@ -211,7 +295,8 @@ namespace vcpkg::PlatformExpression // platform-expression-not = // | platform-expression-simple - // | "!", optional-whitespace, platform-expression-simple ; + // | "!", optional-whitespace, platform-expression-simple + // | "not", platform-expression-unary-keyword-operand ; std::unique_ptr expr_not() { if (cur() == '!') @@ -223,43 +308,99 @@ namespace vcpkg::PlatformExpression // platform-expression-simple return std::make_unique(ExprKind::op_not, expr_simple()); } + else if (cur() == 'n') + { + std::string name = match_zero_or_more(is_identifier_char).to_string(); + + // "not" + if (name == "not") + { + // required-whitespace, platform-expression-simple + // optional-whitespace, platform-expression-grouped + skip_whitespace(); + return std::make_unique(ExprKind::op_not, expr_simple()); + } + + // optional-whitespace + skip_whitespace(); + + return std::make_unique(ExprKind::identifier, std::move(name)); + } // platform-expression-simple return expr_simple(); } + // platform-expression-list = + // | platform-expression {",", optional-whitespace, platform-expression}; + // + // platform-expression-binary-keyword-first-operand = + // | platform-expression-not, required-whitespace + // | platform-expression-grouped ; + // + // platform-expression-binary-keyword-second-operand = + // | required-whitespace, platform-expression-not + // | platform-expression-grouped ; + // // platform-expression-and = - // | platform-expression-not, { "&", optional-whitespace, platform-expression-not } ; + // | platform-expression-not, { "&", optional-whitespace, platform-expression-not } + // | platform-expression-binary-keyword-first-operand, { "and", + // platform-expression-binary-keyword-second-operand } ; // // platform-expression-or = - // | platform-expression-not, { "|", optional-whitespace, platform-expression-not } ; + // | platform-expression-not, { "|", optional-whitespace, platform-expression-not } + // | platform-expression-binary-keyword-first-operand, { "or", + // platform-expression-binary-keyword-second-operand } (* to allow for future extension *) ; // - // already taken care of by the caller: platform-expression-not - // so we start at either "&" or "|" - template + // Processing of the operator was already taken care of by the caller: continue + // with the next platform-expression-not or platform-expression-binary-keyword-second-operand. + template std::unique_ptr expr_binary(std::unique_ptr&& seed) { + // gather consecutive instances of the same operation into a single expr node + // e.g., parsing 'A & B & C' yields {&, vector} + ExprKind next_oper = ExprKind::op_invalid; do { - // Support chains of the operator to avoid breaking backwards compatibility - do - { - // "&" or "|", - next(); - } while (allow_multiple_binary_operators() && cur() == oper); - // optional-whitespace, skip_whitespace(); - // platform-expression-not, (go back to start of repetition) - seed->exprs.push_back(expr_not()); - } while (cur() == oper); - if (cur() == other) + if constexpr (oper == ExprKind::op_list) + { + // platform-expression { ",", optional-whitespace, platform-expression } ; + seed->exprs.push_back(expr()); + } + else + { + // platform-expression-not, (go back to start of repetition) + seed->exprs.push_back(expr_not()); + } + next_oper = expr_operator(); + } while (next_oper == oper); + + if constexpr (unmixable_oper != ExprKind::op_invalid) { - add_error("mixing & and | is not allowed; use () to specify order of operations"); + if (next_oper == unmixable_oper) + { + add_error("mixing & and | is not allowed; use () to specify order of operations"); + } } - return std::move(seed); + if (next_oper == ExprKind::op_list) + { + // platform-expression { ",", optional-whitespace, platform-expression } ; + // + // To handle a lower-precedence, treat the remainder of the string as a platform expression. + // E.g., "A & B , C | D" will be treated as "(A & B) , (C | D)", which preserves intended precedence + // In this case, see is the LHS at the point in which we see the ",". + + return expr_binary( + std::make_unique(next_oper, std::move(seed))); + } + else + { + return std::move(seed); + } } }; } @@ -436,7 +577,7 @@ namespace vcpkg::PlatformExpression return valid; } - else if (expr.kind == ExprKind::op_or) + else if ((expr.kind == ExprKind::op_or) || (expr.kind == ExprKind::op_list)) { bool valid = false; @@ -569,7 +710,10 @@ namespace vcpkg::PlatformExpression case ExprKind::identifier: return expr.identifier; case ExprKind::op_and: join = " & "; break; case ExprKind::op_or: join = " | "; break; + case ExprKind::op_list: join = " , "; break; case ExprKind::op_not: return Strings::format("!%s", (*this)(expr.exprs.at(0))); + case ExprKind::op_empty: join = ""; break; + case ExprKind::op_invalid: join = " invalid "; break; default: Checks::unreachable(VCPKG_LINE_INFO); }