| // Copyright 2025 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "src/main/cpp/sem_ver.h" | |
| #include <cstddef> | |
| #include <optional> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/strings/numbers.h" | |
| #include "absl/strings/str_split.h" | |
| #include "absl/strings/string_view.h" | |
| #include "re2/re2.h" | |
| namespace blaze { | |
| namespace { | |
| // Semantic version regex copied verbatim from | |
| // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string | |
| const LazyRE2 kSemverRe = {R"(^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$)"}; | |
| } // namespace | |
| std::optional<SemVer> SemVer::Parse(const std::string& v) { | |
| int major; | |
| int minor; | |
| int patch; | |
| std::string prerelease; | |
| std::string buildmetadata; | |
| if (RE2::FullMatch(v, *kSemverRe, &major, &minor, &patch, &prerelease, | |
| &buildmetadata)) { | |
| return SemVer(major, minor, patch, prerelease, buildmetadata); | |
| } | |
| return std::nullopt; | |
| } | |
| SemVer SemVer::NextMajorVersion() const { | |
| return SemVer(major_ + 1, 0, 0, "", ""); | |
| } | |
| SemVer SemVer::NextMinorVersion() const { | |
| return SemVer(major_, minor_ + 1, 0, "", ""); | |
| } | |
| int SemVer::Compare(const SemVer& other) const { | |
| auto major_diff = major_ - other.major_; | |
| if (major_diff != 0) { | |
| return major_diff; | |
| } | |
| auto minor_diff = minor_ - other.minor_; | |
| if (minor_diff != 0) { | |
| return minor_diff; | |
| } | |
| auto patch_diff = patch_ - other.patch_; | |
| if (patch_diff != 0) { | |
| return patch_diff; | |
| } | |
| return ComparePrerelease(prerelease_, other.prerelease_); | |
| } | |
| // Adapted from golang's implementation. | |
| // https://cs.opensource.google/go/x/mod/+/refs/tags/v0.30.0:semver/semver.go;l=339;bpv=1 | |
| int SemVer::ComparePrerelease(absl::string_view x, | |
| absl::string_view y) { | |
| if (x == y) { | |
| return 0; | |
| } | |
| // A larger set of pre-release fields has a higher precedence than a smaller | |
| // set. | |
| if (x.empty()) { | |
| return 1; | |
| } | |
| if (y.empty()) { | |
| return -1; | |
| } | |
| // Examine each dot-separated part. | |
| const std::vector<absl::string_view> xv = absl::StrSplit(x, '.'); | |
| const std::vector<absl::string_view> yv = absl::StrSplit(y, '.'); | |
| std::size_t i = 0; | |
| while (i != xv.size() && i != yv.size()) { | |
| auto part_x = xv[i]; | |
| auto part_y = yv[i]; | |
| i++; | |
| if (part_x != part_y) { | |
| int ix; | |
| int iy; | |
| bool is_numeric_x = absl::SimpleAtoi(part_x, &ix); | |
| bool is_numeric_y = absl::SimpleAtoi(part_y, &iy); | |
| if (is_numeric_x != is_numeric_y) { | |
| // Numeric identifiers always have lower precedence than non-numeric | |
| // identifiers. | |
| if (is_numeric_x) { | |
| return -1; | |
| } | |
| return 1; | |
| } | |
| if (is_numeric_x) { // Both parts are numbers. | |
| if (ix < iy) { | |
| return -1; | |
| } | |
| return 1; | |
| } | |
| // Lexicographic ordering. | |
| if (part_x < part_y) { | |
| return -1; | |
| } | |
| return 1; | |
| } | |
| } | |
| // A larger set of pre-release fields has a higher precedence than a smaller | |
| // set. | |
| if (i == xv.size()) { // We ran out of x parts. | |
| return -1; | |
| } | |
| return 1; | |
| } | |
| bool SemVer::operator==(const SemVer& other) const { | |
| return Compare(other) == 0; | |
| } | |
| bool SemVer::operator<(const SemVer& other) const { return Compare(other) < 0; } | |
| bool SemVer::operator!=(const SemVer& other) const { | |
| return Compare(other) != 0; | |
| } | |
| bool SemVer::operator>(const SemVer& other) const { return Compare(other) > 0; } | |
| bool SemVer::operator<=(const SemVer& other) const { | |
| return Compare(other) <= 0; | |
| } | |
| bool SemVer::operator>=(const SemVer& other) const { | |
| return Compare(other) >= 0; | |
| } | |
| } // namespace blaze |