# This file is a part of Julia. License is MIT: https://julialang.org/license ## semantic version numbers (https://semver.org/) const VerTuple = Tuple{Vararg{Union{UInt64,String}}} const VInt = UInt32 """ VersionNumber Version number type which follows the specifications of [semantic versioning (semver)](https://semver.org/), composed of major, minor and patch numeric values, followed by pre-release and build alphanumeric annotations. `VersionNumber` objects can be compared with all of the standard comparison operators (`==`, `<`, `<=`, etc.), with the result following semver rules. `VersionNumber` has the the following public fields: - `v.major::Integer` - `v.minor::Integer` - `v.patch::Integer` - `v.prerelease::Tuple{Vararg{Union{Integer, AbstractString}}}` - `v.build::Tuple{Vararg{Union{Integer, AbstractString}}}` See also [`@v_str`](@ref) to efficiently construct `VersionNumber` objects from semver-format literal strings, [`VERSION`](@ref) for the `VersionNumber` of Julia itself, and [Version Number Literals](@ref man-version-number-literals) in the manual. # Examples ```jldoctest julia> a = VersionNumber(1, 2, 3) v"1.2.3" julia> a >= v"1.2" true julia> b = VersionNumber("2.0.1-rc1") v"2.0.1-rc1" julia> b >= v"2.0.1" false ``` """ struct VersionNumber major::VInt minor::VInt patch::VInt prerelease::VerTuple build::VerTuple function VersionNumber(major::VInt, minor::VInt, patch::VInt, pre::VerTuple, bld::VerTuple) major >= 0 || throw(ArgumentError("invalid negative major version: $major")) minor >= 0 || throw(ArgumentError("invalid negative minor version: $minor")) patch >= 0 || throw(ArgumentError("invalid negative patch version: $patch")) for ident in pre if ident isa Integer ident >= 0 || throw(ArgumentError("invalid negative pre-release identifier: $ident")) else if !occursin(r"^(?:|[0-9a-z-]*[a-z-][0-9a-z-]*)$"i, ident) || isempty(ident) && !(length(pre)==1 && isempty(bld)) throw(ArgumentError("invalid pre-release identifier: $(repr(ident))")) end end end for ident in bld if ident isa Integer ident >= 0 || throw(ArgumentError("invalid negative build identifier: $ident")) else if !occursin(r"^(?:|[0-9a-z-]*[a-z-][0-9a-z-]*)$"i, ident) || isempty(ident) && length(bld)!=1 throw(ArgumentError("invalid build identifier: $(repr(ident))")) end end end new(major, minor, patch, pre, bld) end end VersionNumber(major::Integer, minor::Integer = 0, patch::Integer = 0, pre::Tuple{Vararg{Union{Integer,AbstractString}}} = (), bld::Tuple{Vararg{Union{Integer,AbstractString}}} = ()) = VersionNumber(VInt(major), VInt(minor), VInt(patch), map(x->x isa Integer ? UInt64(x) : String(x), pre), map(x->x isa Integer ? UInt64(x) : String(x), bld)) VersionNumber(v::Tuple) = VersionNumber(v...) VersionNumber(v::VersionNumber) = v function print(io::IO, v::VersionNumber) v == typemax(VersionNumber) && return print(io, "∞") print(io, v.major) print(io, '.') print(io, v.minor) print(io, '.') print(io, v.patch) if !isempty(v.prerelease) print(io, '-') join(io, v.prerelease,'.') end if !isempty(v.build) print(io, '+') join(io, v.build,'.') end end show(io::IO, v::VersionNumber) = print(io, "v\"", v, "\"") Broadcast.broadcastable(v::VersionNumber) = Ref(v) const VERSION_REGEX = r"^ v? # prefix (optional) (\d+) # major (required) (?:\.(\d+))? # minor (optional) (?:\.(\d+))? # patch (optional) (?:(-)| # pre-release (optional) ([a-z][0-9a-z-]*(?:\.[0-9a-z-]+)*|-(?:[0-9a-z-]+\.)*[0-9a-z-]+)? (?:(\+)| (?:\+((?:[0-9a-z-]+\.)*[0-9a-z-]+))? # build (optional) )) $"ix function split_idents(s::AbstractString) idents = eachsplit(s, '.') pidents = Union{UInt64,String}[occursin(r"^\d+$", ident) ? parse(UInt64, ident) : String(ident) for ident in idents] return tuple(pidents...)::VerTuple end function tryparse(::Type{VersionNumber}, v::AbstractString) v == "∞" && return typemax(VersionNumber) m = match(VERSION_REGEX, String(v)::String) m === nothing && return nothing major, minor, patch, minus, prerl, plus, build = m.captures major = parse(VInt, major::AbstractString) minor = minor !== nothing ? parse(VInt, minor) : VInt(0) patch = patch !== nothing ? parse(VInt, patch) : VInt(0) if prerl !== nothing && !isempty(prerl) && prerl[1] == '-' prerl = prerl[2:end] # strip leading '-' end prerl = prerl !== nothing ? split_idents(prerl) : minus !== nothing ? ("",) : () build = build !== nothing ? split_idents(build) : plus !== nothing ? ("",) : () return VersionNumber(major, minor, patch, prerl::VerTuple, build::VerTuple) end function parse(::Type{VersionNumber}, v::AbstractString) ver = tryparse(VersionNumber, v) ver === nothing && throw(ArgumentError("invalid version string: $v")) return ver end VersionNumber(v::AbstractString) = parse(VersionNumber, v) """ @v_str String macro used to parse a string to a [`VersionNumber`](@ref). # Examples ```jldoctest julia> v"1.2.3" v"1.2.3" julia> v"2.0.1-rc1" v"2.0.1-rc1" ``` """ macro v_str(v); VersionNumber(v); end function typemax(::Type{VersionNumber}) ∞ = typemax(VInt) VersionNumber(∞, ∞, ∞, (), ("",)) end typemin(::Type{VersionNumber}) = v"0-" ident_cmp(a::Integer, b::Integer) = cmp(a, b) ident_cmp(a::Integer, b::String ) = isempty(b) ? +1 : -1 ident_cmp(a::String, b::Integer) = isempty(a) ? -1 : +1 ident_cmp(a::String, b::String ) = cmp(a, b) function ident_cmp(A::VerTuple, B::VerTuple) for (a, b) in Iterators.Zip{Tuple{VerTuple,VerTuple}}((A, B)) c = ident_cmp(a, b) (c != 0) && return c end length(A) < length(B) ? -1 : length(B) < length(A) ? +1 : 0 end function ==(a::VersionNumber, b::VersionNumber) (a.major != b.major) && return false (a.minor != b.minor) && return false (a.patch != b.patch) && return false (ident_cmp(a.prerelease, b.prerelease) != 0) && return false (ident_cmp(a.build, b.build) != 0) && return false return true end issupbuild(v::VersionNumber) = length(v.build)==1 && isempty(v.build[1]) function isless(a::VersionNumber, b::VersionNumber) (a.major < b.major) && return true (a.major > b.major) && return false (a.minor < b.minor) && return true (a.minor > b.minor) && return false (a.patch < b.patch) && return true (a.patch > b.patch) && return false (!isempty(a.prerelease) && isempty(b.prerelease)) && return true (isempty(a.prerelease) && !isempty(b.prerelease)) && return false c = ident_cmp(a.prerelease,b.prerelease) (c < 0) && return true (c > 0) && return false (!issupbuild(a) && issupbuild(b)) && return true (issupbuild(a) && !issupbuild(b)) && return false c = ident_cmp(a.build,b.build) (c < 0) && return true return false end function hash(v::VersionNumber, h::UInt) h += 0x8ff4ffdb75f9fede % UInt h = hash(v.major, h) h = hash(v.minor, h) h = hash(v.patch, h) h = hash(v.prerelease, ~h) h = hash(v.build, ~h) end lowerbound(v::VersionNumber) = VersionNumber(v.major, v.minor, v.patch, ("",), ()) upperbound(v::VersionNumber) = VersionNumber(v.major, v.minor, v.patch, (), ("",)) thispatch(v::VersionNumber) = VersionNumber(v.major, v.minor, v.patch) thisminor(v::VersionNumber) = VersionNumber(v.major, v.minor, 0) thismajor(v::VersionNumber) = VersionNumber(v.major, 0, 0) nextpatch(v::VersionNumber) = v < thispatch(v) ? thispatch(v) : VersionNumber(v.major, v.minor, v.patch+1) nextminor(v::VersionNumber) = v < thisminor(v) ? thisminor(v) : VersionNumber(v.major, v.minor+1, 0) nextmajor(v::VersionNumber) = v < thismajor(v) ? thismajor(v) : VersionNumber(v.major+1, 0, 0) ## julia version info """ VERSION A [`VersionNumber`](@ref) object describing which version of Julia is in use. See also [Version Number Literals](@ref man-version-number-literals). """ const VERSION = try ver = VersionNumber(VERSION_STRING) if !isempty(ver.prerelease) && !GIT_VERSION_INFO.tagged_commit if GIT_VERSION_INFO.build_number >= 0 ver = VersionNumber(ver.major, ver.minor, ver.patch, (ver.prerelease..., GIT_VERSION_INFO.build_number), ver.build) else println("WARNING: no build number found for pre-release version") ver = VersionNumber(ver.major, ver.minor, ver.patch, (ver.prerelease..., "unknown"), ver.build) end elseif GIT_VERSION_INFO.build_number > 0 println("WARNING: ignoring non-zero build number for VERSION") end ver catch e println("while creating Base.VERSION, ignoring error $e") VersionNumber(0) end const libllvm_version = if endswith(libllvm_version_string, "jl") # strip the "jl" SONAME suffix (JuliaLang/julia#33058) # (LLVM does never report a prerelease version anyway) VersionNumber(libllvm_version_string[1:end-2]) else VersionNumber(libllvm_version_string) end libllvm_path() = ccall(:jl_get_libllvm, Any, ())