Version 모듈
Version 모듈
버전을 파싱하고, 그 버전이 특정 요구사항(requirement)과 매칭되는지 확인해야 할 때 쓰는 모듈이 바로 Version이에요. SemVer 형식의 버전 문자열을 다룰 때 가장 흔히 마주치는 모듈이죠. 예를 들어 어떤 패키지가 "2.0 이상 2.1 미만"을 요구할 때 그 조건을 검사하는 식으로 사용해요.
본문
Version 모듈은 버전을 파싱하고 요구사항과 매칭하는 함수를 제공해요. 버전은 특정 형식의 문자열이거나, Version.parse/1로 파싱한 뒤 생성된 Version이에요.
Elixir 프로젝트가 SemVer를 반드시 따라야 하는 것은 아니지만, SemVer 2.0 스키마에 명시된 형식은 따라야 해요.
버전 (Versions)
요약하면 버전은 세 개의 숫자로 표현돼요.
MAJOR.MINOR.PATCH
각 숫자 구성 요소는 최대 14자리로 제한돼요.
프리릴리스(pre-release)는 패치 버전 바로 뒤에 하이픈과 마침표로 구분된 일련의 식별자를 선택적으로 붙여 지원돼요. 식별자는 ASCII 영숫자 문자와 하이픈([0-9A-Za-z-])으로만 구성돼요.
"1.0.0-alpha.3"
숫자형 프리릴리스 식별자도 최대 14자리로 제한돼요.
빌드 정보는 패치 또는 프리릴리스 버전 바로 뒤에 플러스 기호와 마침표로 구분된 일련의 식별자를 붙여 추가할 수 있어요. 식별자는 ASCII 영숫자 문자와 하이픈([0-9A-Za-z-])으로만 구성돼요.
"1.0.0-alpha.3+201****0000.amd64"
요구사항 (Requirements)
요구사항(requirement)을 쓰면 주어진 의존성(dependency)의 어떤 버전들과 함께 작업할 의향이 있는지를 지정할 수 있어요. 요구사항은 >, >=, <, <=, == 같은 일반적인 비교 연산자를 지원하고, 추가로 아래에서 자세히 설명할 특별한 연산자 ~>를 지원해요.
# 2.0.0 버전만
"== 2.0.0"
# 2.0.0보다 이후의 모든 것
"> 2.0.0"
연산자를 생략할 수도 있는데, 이 경우 ==와 동일해요.
# 2.0.0 버전만
"2.0.0"
요구사항은 복잡한 조건을 위해 and와 or도 지원해요.
# 2.0.0부터 2.1.0까지
">= 2.0.0 and < 2.1.0"
위 예시는 매우 흔한 요구사항이라 다음과 같이 표현할 수 있어요.
"~> 2.0.0"
~>는 :allow_pre 옵션의 사용 여부나 피연산자가 프리릴리스 버전인지와 관계없이, 상한(upper bound)의 프리릴리스 버전을 절대 포함하지 않아요. 또한 major 버전 부분에만 상한을 설정하는 데도 쓸 수 있어요. ~> 요구사항과 그에 해당하는 변환은 아래 표를 참고하세요.
~> |
변환 (Translation) |
|---|---|
~> 2.0.0 |
>= 2.0.0 and < 2.1.0 |
~> 2.1.2 |
>= 2.1.2 and < 2.2.0 |
~> 2.1.3-dev |
>= 2.1.3-dev and < 2.2.0 |
~> 2.0 |
>= 2.0.0 and < 3.0.0 |
~> 2.1 |
>= 2.1.0 and < 3.0.0 |
~> 뒤의 요구사항 피연산자는 패치 버전을 생략할 수 있어요. 그래서 ~> 2.1이나 ~> 2.1-dev를 표현할 수 있는데, 일반적인 비교 연산자를 쓸 때는 이렇게 할 수 없어요.
Version.match?/3에서 :allow_pre 옵션이 false로 설정되면, 피연산자가 프리릴리스 버전이 아닌 한 요구사항은 프리릴리스 버전과 매칭되지 않아요. 기본값은 항상 프리릴리스를 허용하는 것이지만, Hex에서는 :allow_pre가 false로 설정돼 있다는 점을 유의하세요. 예시는 아래 표를 참고하세요.
| 요구사항 | 버전 | :allow_pre |
매칭 여부 |
|---|---|---|---|
~> 2.0 |
2.1.0 |
true or false |
true |
~> 2.0 |
3.0.0 |
true or false |
false |
~> 2.0.0 |
2.0.5 |
true or false |
true |
~> 2.0.0 |
2.1.0 |
true or false |
false |
~> 2.1.2 |
2.1.6-dev |
true |
true |
~> 2.1.2 |
2.1.6-dev |
false |
false |
~> 2.1-dev |
2.2.0-dev |
true or false |
true |
~> 2.1.2-dev |
2.1.6-dev |
true or false |
true |
>= 2.1.0 |
2.2.0-dev |
true |
true |
>= 2.1.0 |
2.2.0-dev |
false |
false |
>= 2.1.0-dev |
2.2.6-dev |
true or false |
true |
Version 구조체 (The Version struct)
Version 구조체는 SemVer 2.0에 따라 :major, :minor, :patch, :pre, :build 필드를 담아요. 여기서 :pre는 리스트예요.
@type t() :: %Version{
build: build(),
major: major(),
minor: minor(),
patch: patch(),
pre: pre()
}
이 필드들은 읽을 수 있지만, 구조체 문법으로 새 Version을 직접 만들지는 않아야 해요. 대신 이 모듈의 함수를 사용하세요.
관련 타입 정의는 다음과 같아요.
@type build() :: String.t() | nil
@type major() :: non_neg_integer()
@type minor() :: non_neg_integer()
@type patch() :: non_neg_integer()
@type pre() :: [String.t() | non_neg_integer()]
@type version() :: String.t() | t()
@type match_opts() :: [{:allow_pre, boolean()}]
@type requirement() :: String.t() | Version.Requirement.t()
compare(version1, version2)
두 버전을 비교해요.
@spec compare(version(), version()) :: :gt | :eq | :lt
첫 번째 버전이 두 번째보다 크면 :gt를, 그 반대면 :lt를 반환해요. 두 버전이 같으면 :eq를 반환해요.
- 프리릴리스는 해당 정식(release) 버전보다 엄격히 작아요.
- 패치 세그먼트는 영숫자면 사전순으로, 그렇지 않으면 수치로 비교돼요.
- 빌드 세그먼트는 무시돼요. 두 버전이 빌드 세그먼트에서만 다르다면 같은 것으로 간주해요.
주어진 두 버전 중 하나라도 파싱할 수 없으면 Version.InvalidVersionError 예외를 던져요. 이미 파싱된 버전이 주어지면 이 함수는 던지지 않아요.
iex> Version.compare("2.0.1-alpha1", "2.0.0")
:gt
iex> Version.compare("1.0.0-beta", "1.0.0-rc1")
:lt
iex> Version.compare("1.0.0-10", "1.0.0-2")
:gt
iex> Version.compare("2.0.1+build0", "2.0.1")
:eq
iex> Version.compare("invalid", "2.0.1")
** (Version.InvalidVersionError) invalid version: "invalid"
compile_requirement(requirement)
요구사항을 매칭을 최적화할 수 있는 내부 표현으로 컴파일해요.
@spec compile_requirement(Version.Requirement.t()) :: Version.Requirement.t()
내부 표현은 불투명(opaque)해요.
match?(version, requirement, opts \ [])
주어진 버전이 명세와 매칭되는지 확인해요.
@spec match?(version(), requirement(), match_opts()) :: boolean()
version이 requirement를 충족하면 true를, 그렇지 않으면 false를 반환해요. requirement가 파싱할 수 없으면 Version.InvalidRequirementError를, version이 파싱할 수 없으면 Version.InvalidVersionError를 던져요. 이미 파싱된 버전과 요구사항이 주어지면 이 함수는 던지지 않아요.
옵션 (Options)
:allow_pre(boolean) -false면 피연산자가 프리릴리스 버전이 아닌 한 프리릴리스 버전은 매칭되지 않아요. 기본값은true예요. 예시는 위 "요구사항" 섹션의 표를 참고하세요.
iex> Version.match?("2.0.0", "> 1.0.0")
true
iex> Version.match?("2.0.0", "== 1.0.0")
false
iex> Version.match?("2.1.6-dev", "~> 2.1.2")
true
iex> Version.match?("2.1.6-dev", "~> 2.1.2", allow_pre: false)
false
iex> Version.match?("foo", "== 1.0.0")
** (Version.InvalidVersionError) invalid version: "foo"
iex> Version.match?("2.0.0", "== == 1.0.0")
** (Version.InvalidRequirementError) invalid requirement: "== == 1.0.0"
parse(string)
버전 문자열을 Version 구조체로 파싱해요.
@spec parse(String.t()) :: {:ok, t()} | :error
iex> Version.parse("2.0.1-alpha1")
{:ok, %Version{major: 2, minor: 0, patch: 1, pre: ["alpha1"]}}
iex> Version.parse("2.0-alpha1")
:error
parse!(string)
버전 문자열을 Version으로 파싱해요.
@spec parse!(String.t()) :: t()
string이 유효하지 않은 버전이면 Version.InvalidVersionError를 던져요.
iex> Version.parse!("2.0.1-alpha1")
%Version{major: 2, minor: 0, patch: 1, pre: ["alpha1"]}
iex> Version.parse!("2.0-alpha1")
** (Version.InvalidVersionError) invalid version: "2.0-alpha1"
parse_requirement(string)
버전 요구사항 문자열을 Version.Requirement 구조체로 파싱해요.
@spec parse_requirement(String.t()) :: {:ok, Version.Requirement.t()} | :error
iex> {:ok, requirement} = Version.parse_requirement("== 2.0.1")
iex> requirement
Version.parse_requirement!("== 2.0.1")
iex> Version.parse_requirement("== == 2.0.1")
:error
parse_requirement!(string) (since 1.8.0)
버전 요구사항 문자열을 Version.Requirement 구조체로 파싱해요.
@spec parse_requirement!(String.t()) :: Version.Requirement.t()
string이 유효하지 않은 요구사항이면 Version.InvalidRequirementError를 던져요.
iex> Version.parse_requirement!("== 2.0.1")
Version.parse_requirement!("== 2.0.1")
iex> Version.parse_requirement!("== == 2.0.1")
** (Version.InvalidRequirementError) invalid requirement: "== == 2.0.1"
to_string(version) (since 1.14.0)
주어진 버전을 문자열로 변환해요.
@spec to_string(t()) :: String.t()
iex> Version.to_string(%Version{major: 1, minor: 2, patch: 3})
"1.2.3"
iex> Version.to_string(Version.parse!("1.14.0-rc.0+build0"))
"1.14.0-rc.0+build0"