//===--- Version.cpp - Swift Version Number -------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// // // This file defines several version-related utility functions for Swift. // //===----------------------------------------------------------------------===// #include "clang/Basic/CharInfo.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" #include "swift/AST/DiagnosticsParse.h" #include "swift/Basic/LLVM.h" #include "swift/Basic/Version.h" #include #define TOSTR2(X) #X #define TOSTR(X) TOSTR2(X) #ifdef SWIFT_VERSION_PATCHLEVEL /// Helper macro for SWIFT_VERSION_STRING. #define SWIFT_MAKE_VERSION_STRING(X, Y, Z) TOSTR(X) "." TOSTR(Y) "." TOSTR(Z) /// A string that describes the Swift version number, e.g., "1.0". #define SWIFT_VERSION_STRING \ SWIFT_MAKE_VERSION_STRING(SWIFT_VERSION_MAJOR, SWIFT_VERSION_MINOR, \ SWIFT_VERSION_PATCHLEVEL) #else /// Helper macro for SWIFT_VERSION_STRING. #define SWIFT_MAKE_VERSION_STRING(X, Y) TOSTR(X) "." TOSTR(Y) /// A string that describes the Swift version number, e.g., "1.0". #define SWIFT_VERSION_STRING \ SWIFT_MAKE_VERSION_STRING(SWIFT_VERSION_MAJOR, SWIFT_VERSION_MINOR) #endif #include "LLVMRevision.inc" #include "SwiftRevision.inc" namespace swift { namespace version { /// Print a string of the form "LLVM xxxxx, Swift zzzzz", where each placeholder /// is the revision for the associated repository. static void printFullRevisionString(raw_ostream &out) { // Arbitrarily truncate to 15 characters. This should be enough to unique Git // hashes while keeping the REPL version string from overflowing 80 columns. #if defined(LLVM_REVISION) out << "LLVM " << StringRef(LLVM_REVISION).slice(0, 15); # if defined(SWIFT_REVISION) out << ", "; # endif #endif #if defined(SWIFT_REVISION) out << "Swift " << StringRef(SWIFT_REVISION).slice(0, 15); #endif } static void splitVersionComponents( SmallVectorImpl> &SplitComponents, StringRef &VersionString, SourceLoc Loc, bool skipQuote = false) { SourceLoc Start = (Loc.isValid() && skipQuote) ? Loc.getAdvancedLoc(1) : Loc; SourceLoc End = Start; // Split the version string into tokens separated by the '.' character. while (!VersionString.empty()) { StringRef SplitComponent, Rest; std::tie(SplitComponent, Rest) = VersionString.split('.'); if (Loc.isValid()) { End = End.getAdvancedLoc(SplitComponent.size()); } auto Range = Loc.isValid() ? SourceRange(Start, End) : SourceRange(); if (Loc.isValid()) End = End.getAdvancedLoc(1); Start = End; SplitComponents.push_back({SplitComponent, Range}); VersionString = Rest; } } Optional Version::parseCompilerVersionString( StringRef VersionString, SourceLoc Loc, DiagnosticEngine *Diags) { Version CV; SmallString<16> digits; llvm::raw_svector_ostream OS(digits); SmallVector, 5> SplitComponents; splitVersionComponents(SplitComponents, VersionString, Loc, /*skipQuote=*/true); uint64_t ComponentNumber; bool isValidVersion = true; auto checkVersionComponent = [&](unsigned Component, SourceRange Range) { unsigned limit = CV.Components.empty() ? 9223371 : 999; if (Component > limit) { if (Diags) Diags->diagnose(Range.Start, diag::compiler_version_component_out_of_range, limit); isValidVersion = false; } }; for (size_t i = 0; i < SplitComponents.size(); ++i) { StringRef SplitComponent; SourceRange Range; std::tie(SplitComponent, Range) = SplitComponents[i]; // Version components can't be empty. if (SplitComponent.empty()) { if (Diags) Diags->diagnose(Range.Start, diag::empty_version_component); isValidVersion = false; continue; } // The second version component isn't used for comparison. if (i == 1) { if (!SplitComponent.equals("*")) { if (Diags) { // Majors 600-1300 were used for Swift 1.0-5.5 (based on clang // versions), but then we reset the numbering based on Swift versions, // so 5.6 had major 5. We assume that majors below 600 use the new // scheme and equal/above it use the old scheme. bool firstComponentLooksNew = CV.Components[0] < 600; auto diag = Diags->diagnose(Range.Start, diag::unused_compiler_version_component, firstComponentLooksNew); if (firstComponentLooksNew && !SplitComponent.getAsInteger(10, ComponentNumber)) { // Fix-it version like "5.7.1.2.3" to "5007.*.1.2.3". auto newDigits = llvm::formatv("{0}{1,0+3}.*", CV.Components[0], ComponentNumber).str(); diag.fixItReplaceChars(SplitComponents[0].second.Start, Range.End, newDigits); } else { diag.fixItReplaceChars(Range.Start, Range.End, "*"); } } } CV.Components.push_back(0); continue; } // All other version components must be numbers. if (!SplitComponent.getAsInteger(10, ComponentNumber)) { checkVersionComponent(ComponentNumber, Range); CV.Components.push_back(ComponentNumber); continue; } else { if (Diags) Diags->diagnose(Range.Start, diag::version_component_not_number); isValidVersion = false; } } if (CV.Components.size() > 5) { if (Diags) Diags->diagnose(Loc, diag::compiler_version_too_many_components); isValidVersion = false; } // In the beginning, '_compiler_version(string-literal)' was designed for a // different version scheme where the major was fairly large and the minor // was ignored; now we use one where the minor is significant and major and // minor match the Swift language version. See the comment above on // `firstComponentLooksNew` for details. // // However, we want the string literal variant of '_compiler_version' to // maintain source compatibility with old checks; that means checks for new // versions have to be written so that old compilers will think they represent // newer versions, while new compilers have to interpret old version number // strings in a way that will compare correctly to the new versions compiled // into them. // // To achieve this, modern compilers divide the major by 1000 and overwrite // the wildcard component with the remainder, effectively shifting the last // three digits of the major into the minor, before comparing it to the // compiler version: // // _compiler_version("5007.*.1.2.3") -> 5.7.1.2.3 // _compiler_version("1300.*.1.2.3") -> 1.300.1.2.3 (smaller than 5.6) // _compiler_version( "600.*.1.2.3") -> 0.600.1.2.3 (smaller than 5.6) // // So if you want to specify a 5.7.z.a.b version, we ask users to either write // it as 5007.*.z.a.b, or to use the new '_compiler_version(>= version)' // syntax instead, which does not perform this conversion. if (!CV.Components.empty()) { if (CV.Components.size() == 1) CV.Components.push_back(0); CV.Components[1] = CV.Components[0] % 1000; CV.Components[0] = CV.Components[0] / 1000; } return isValidVersion ? Optional(CV) : None; } Optional Version::parseVersionString(StringRef VersionString, SourceLoc Loc, DiagnosticEngine *Diags) { Version TheVersion; SmallString<16> digits; llvm::raw_svector_ostream OS(digits); SmallVector, 5> SplitComponents; // Skip over quote character in string literal. if (VersionString.empty()) { if (Diags) Diags->diagnose(Loc, diag::empty_version_string); return None; } splitVersionComponents(SplitComponents, VersionString, Loc, Diags); uint64_t ComponentNumber; bool isValidVersion = true; for (size_t i = 0; i < SplitComponents.size(); ++i) { StringRef SplitComponent; SourceRange Range; std::tie(SplitComponent, Range) = SplitComponents[i]; // Version components can't be empty. if (SplitComponent.empty()) { if (Diags) Diags->diagnose(Range.Start, diag::empty_version_component); isValidVersion = false; continue; } // All other version components must be numbers. if (!SplitComponent.getAsInteger(10, ComponentNumber)) { TheVersion.Components.push_back(ComponentNumber); continue; } else { if (Diags) Diags->diagnose(Range.Start, diag::version_component_not_number); isValidVersion = false; } } return isValidVersion ? Optional(TheVersion) : None; } Version::Version(StringRef VersionString, SourceLoc Loc, DiagnosticEngine *Diags) : Version(*parseVersionString(VersionString, Loc, Diags)) {} Version Version::getCurrentCompilerVersion() { #ifdef SWIFT_COMPILER_VERSION auto currentVersion = Version::parseVersionString( SWIFT_COMPILER_VERSION, SourceLoc(), nullptr); assert(currentVersion.hasValue() && "Embedded Swift language version couldn't be parsed: '" SWIFT_COMPILER_VERSION "'"); return currentVersion.getValue(); #else return Version(); #endif } Version Version::getCurrentLanguageVersion() { #if SWIFT_VERSION_PATCHLEVEL return {SWIFT_VERSION_MAJOR, SWIFT_VERSION_MINOR, SWIFT_VERSION_PATCHLEVEL}; #else return {SWIFT_VERSION_MAJOR, SWIFT_VERSION_MINOR}; #endif } raw_ostream &operator<<(raw_ostream &os, const Version &version) { if (version.empty()) return os; os << version[0]; for (size_t i = 1, e = version.size(); i != e; ++i) os << '.' << version[i]; return os; } std::string Version::preprocessorDefinition(StringRef macroName, ArrayRef componentWeights) const { uint64_t versionConstant = 0; for (size_t i = 0, e = std::min(componentWeights.size(), Components.size()); i < e; ++i) { versionConstant += componentWeights[i] * Components[i]; } std::string define("-D"); llvm::raw_string_ostream(define) << macroName << '=' << versionConstant; // This isn't using stream.str() so that we get move semantics. return define; } Version::operator llvm::VersionTuple() const { switch (Components.size()) { case 0: return llvm::VersionTuple(); case 1: return llvm::VersionTuple((unsigned)Components[0]); case 2: return llvm::VersionTuple((unsigned)Components[0], (unsigned)Components[1]); case 3: return llvm::VersionTuple((unsigned)Components[0], (unsigned)Components[1], (unsigned)Components[2]); case 4: case 5: return llvm::VersionTuple((unsigned)Components[0], (unsigned)Components[1], (unsigned)Components[2], (unsigned)Components[3]); default: llvm_unreachable("swift::version::Version with 6 or more components"); } } Optional Version::getEffectiveLanguageVersion() const { switch (size()) { case 0: return None; case 1: break; case 2: // The only valid explicit language version with a minor // component is 4.2. if (Components[0] == 4 && Components[1] == 2) break; return None; default: // We do not want to permit users requesting more precise effective language // versions since accepting such an argument promises more than we're able // to deliver. return None; } // FIXME: When we switch to Swift 5 by default, the "4" case should return // a version newer than any released 4.x compiler, and the // "5" case should start returning getCurrentLanguageVersion. We should // also check for the presence of SWIFT_VERSION_PATCHLEVEL, and if that's // set apply it to the "3" case, so that Swift 4.0.1 will automatically // have a compatibility mode of 3.2.1. switch (Components[0]) { case 4: // Version '4' on its own implies '4.1.50'. if (size() == 1) return Version{4, 1, 50}; // This should be true because of the check up above. assert(size() == 2 && Components[0] == 4 && Components[1] == 2); return Version{4, 2}; case 5: static_assert(SWIFT_VERSION_MAJOR == 5, "getCurrentLanguageVersion is no longer correct here"); return Version::getCurrentLanguageVersion(); case 6: // Allow version '6' in asserts compilers *only* so that we can start // testing changes slated for Swift 6. Note that it's still not listed in // `Version::getValidEffectiveVersions()`. // FIXME: When Swift 6 becomes real, remove 'REQUIRES: asserts' from tests // using '-swift-version 6'. #ifdef NDEBUG LLVM_FALLTHROUGH; #else return Version{6}; #endif default: return None; } } Version Version::asMajorVersion() const { if (empty()) return {}; Version res; res.Components.push_back(Components[0]); return res; } std::string Version::asAPINotesVersionString() const { // Other than for "4.2.x", map the Swift major version into // the API notes version for Swift. This has the effect of allowing // API notes to effect changes only on Swift major versions, // not minor versions. if (size() >= 2 && Components[0] == 4 && Components[1] == 2) return "4.2"; return llvm::itostr(Components[0]); } bool operator>=(const class Version &lhs, const class Version &rhs) { // The empty compiler version represents the latest possible version, // usually built from the source repository. if (lhs.empty()) return true; auto n = std::max(lhs.size(), rhs.size()); for (size_t i = 0; i < n; ++i) { auto lv = i < lhs.size() ? lhs[i] : 0; auto rv = i < rhs.size() ? rhs[i] : 0; if (lv < rv) return false; else if (lv > rv) return true; } // Equality return true; } bool operator<(const class Version &lhs, const class Version &rhs) { return !(lhs >= rhs); } bool operator==(const class Version &lhs, const class Version &rhs) { auto n = std::max(lhs.size(), rhs.size()); for (size_t i = 0; i < n; ++i) { auto lv = i < lhs.size() ? lhs[i] : 0; auto rv = i < rhs.size() ? rhs[i] : 0; if (lv != rv) return false; } return true; } std::pair getSwiftNumericVersion() { return { SWIFT_VERSION_MAJOR, SWIFT_VERSION_MINOR }; } std::string getSwiftFullVersion(Version effectiveVersion) { std::string buf; llvm::raw_string_ostream OS(buf); #ifdef SWIFT_VENDOR OS << SWIFT_VENDOR " "; #endif OS << "Swift version " SWIFT_VERSION_STRING; #ifndef SWIFT_COMPILER_VERSION OS << "-dev"; #endif if (effectiveVersion != Version::getCurrentLanguageVersion()) { OS << " effective-" << effectiveVersion; } #if defined(SWIFT_COMPILER_VERSION) OS << " (swiftlang-" SWIFT_COMPILER_VERSION; #if defined(CLANG_COMPILER_VERSION) OS << " clang-" CLANG_COMPILER_VERSION; #endif OS << ")"; #elif defined(LLVM_REVISION) || defined(SWIFT_REVISION) OS << " ("; printFullRevisionString(OS); OS << ")"; #endif // Suppress unused function warning (void)&printFullRevisionString; return OS.str(); } StringRef getSwiftRevision() { #ifdef SWIFT_REVISION return SWIFT_REVISION; #else return ""; #endif } bool isCurrentCompilerTagged() { #ifdef SWIFT_COMPILER_VERSION return true; #else return false; #endif } StringRef getCurrentCompilerTag() { #ifdef SWIFT_COMPILER_VERSION return SWIFT_COMPILER_VERSION; #else return StringRef(); #endif } } // end namespace version } // end namespace swift