summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKalle Lindström <karl.lindstrom@mullvad.net>2024-11-28 12:37:45 +0100
committerDavid Göransson <david.goransson@mullvad.net>2024-11-28 16:10:55 +0100
commitd917673164124cbac31a4cea0737618be2736ae4 (patch)
treec19a1b0db8d6c80586996dcdcf1e891e698863ed
parent7cce5806b4b4362b7141a34f9cfc311deea99dbd (diff)
downloadmullvadvpn-d917673164124cbac31a4cea0737618be2736ae4.tar.xz
mullvadvpn-d917673164124cbac31a4cea0737618be2736ae4.zip
Support Android app's new version code format
-rw-r--r--mullvad-version/src/lib.rs195
-rw-r--r--mullvad-version/src/main.rs94
-rwxr-xr-xprepare-release.sh5
3 files changed, 260 insertions, 34 deletions
diff --git a/mullvad-version/src/lib.rs b/mullvad-version/src/lib.rs
index 71cb27e55d..ce493ea1d7 100644
--- a/mullvad-version/src/lib.rs
+++ b/mullvad-version/src/lib.rs
@@ -2,6 +2,7 @@ use std::fmt::Display;
use std::str::FromStr;
use std::sync::LazyLock;
+use crate::PreStableType::{Alpha, Beta};
use regex::Regex;
/// The Mullvad VPN app product version
@@ -11,27 +12,66 @@ pub const VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/product-versio
pub struct Version {
pub year: String,
pub incremental: String,
- pub beta: Option<String>,
+ /// A version can have an optional pre-stable type, e.g. alpha or beta. If `pre_stable`
+ /// and `dev` both are None the version is stable.
+ pub pre_stable: Option<PreStableType>,
+ /// All versions may have an optional -dev-[commit hash] suffix.
+ pub dev: Option<String>,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum PreStableType {
+ Alpha(String),
+ Beta(String),
}
impl Version {
pub fn parse(version: &str) -> Version {
Version::from_str(version).unwrap()
}
+
+ pub fn is_stable(&self) -> bool {
+ self.pre_stable.is_none() && self.dev.is_none()
+ }
+
+ pub fn alpha(&self) -> Option<&str> {
+ match &self.pre_stable {
+ Some(PreStableType::Alpha(v)) => Some(v),
+ _ => None,
+ }
+ }
+
+ pub fn beta(&self) -> Option<&str> {
+ match &self.pre_stable {
+ Some(PreStableType::Beta(beta)) => Some(beta),
+ _ => None,
+ }
+ }
}
impl Display for Version {
- /// Format Version as a string: year.incremental{-beta}
+ /// Format Version as a string: year.incremental-{alpha|beta}-{dev}
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Version {
year,
incremental,
- beta,
+ pre_stable,
+ dev,
} = &self;
- match beta {
- Some(beta) => write!(f, "{year}.{incremental}-{beta}"),
- None => write!(f, "{year}.{incremental}"),
+
+ write!(f, "{year}.{incremental}")?;
+
+ match pre_stable {
+ Some(PreStableType::Alpha(version)) => write!(f, "-alpha{version}")?,
+ Some(PreStableType::Beta(version)) => write!(f, "-beta{version}")?,
+ None => (),
+ };
+
+ if let Some(commit_hash) = dev {
+ write!(f, "-dev-{commit_hash}")?;
}
+
+ Ok(())
}
}
@@ -39,25 +79,152 @@ impl FromStr for Version {
type Err = String;
fn from_str(version: &str) -> Result<Self, Self::Err> {
- const VERSION_REGEX: &str =
- r"^20([0-9]{2})\.([1-9][0-9]?)(-beta([1-9][0-9]?))?(-dev-[0-9a-f]+)?$";
- static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(VERSION_REGEX).unwrap());
+ static VERSION_REGEX: LazyLock<Regex> = LazyLock::new(|| {
+ Regex::new(
+ r"(?x) # enable insignificant whitespace mode
+ 20(?<year>\d{2})\. # the last two digits of the year
+ (?<incremental>[1-9]\d?) # the incrementing version number
+ (?: # (optional) alpha or beta or dev
+ -alpha(?<alpha>[1-9]\d?\d?)|
+ -beta(?<beta>[1-9]\d?\d?)
+ )?
+ (?:
+ -dev-(?<dev>[0-9a-f]+)
+ )?$
+ ",
+ )
+ .unwrap()
+ });
- let captures = RE
+ let captures = VERSION_REGEX
.captures(version)
.ok_or_else(|| format!("Version does not match expected format: {version}"))?;
- let year = captures.get(1).expect("Missing year").as_str().to_owned();
+
+ let year = captures
+ .name("year")
+ .expect("Missing year")
+ .as_str()
+ .to_owned();
+
let incremental = captures
- .get(2)
+ .name("incremental")
.ok_or("Missing incremental")?
.as_str()
.to_owned();
- let beta = captures.get(4).map(|m| m.as_str().to_owned());
+
+ let alpha = captures.name("alpha").map(|m| m.as_str().to_owned());
+ let beta = captures.name("beta").map(|m| m.as_str().to_owned());
+ let dev = captures.name("dev").map(|m| m.as_str().to_owned());
+
+ let pre_stable = match (alpha, beta) {
+ (None, None) => None,
+ (Some(v), None) => Some(Alpha(v)),
+ (None, Some(v)) => Some(Beta(v)),
+ _ => return Err(format!("Invalid version: {version}")),
+ };
Ok(Version {
year,
incremental,
- beta,
+ pre_stable,
+ dev,
})
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_parse() {
+ let version = "2021.34";
+ let parsed = Version::parse(version);
+ assert_eq!(parsed.year, "21");
+ assert_eq!(parsed.incremental, "34");
+ assert_eq!(parsed.alpha(), None);
+ assert_eq!(parsed.beta(), None);
+ assert_eq!(parsed.dev, None);
+ assert!(parsed.is_stable());
+ }
+
+ #[test]
+ fn test_parse_with_alpha() {
+ let version = "2023.1-alpha77";
+ let parsed = Version::parse(version);
+ assert_eq!(parsed.year, "23");
+ assert_eq!(parsed.incremental, "1");
+ assert_eq!(parsed.alpha(), Some("77"));
+ assert_eq!(parsed.beta(), None);
+ assert_eq!(parsed.dev, None);
+ assert!(!parsed.is_stable());
+
+ let version = "2021.34-alpha777";
+ let parsed = Version::parse(version);
+ assert_eq!(parsed.alpha(), Some("777"));
+ }
+
+ #[test]
+ fn test_parse_with_beta() {
+ let version = "2021.34-beta5";
+ let parsed = Version::parse(version);
+ assert_eq!(parsed.year, "21");
+ assert_eq!(parsed.incremental, "34");
+ assert_eq!(parsed.alpha(), None);
+ assert_eq!(parsed.beta(), Some("5"));
+ assert_eq!(parsed.dev, None);
+ assert!(!parsed.is_stable());
+
+ let version = "2021.34-beta453";
+ let parsed = Version::parse(version);
+ assert_eq!(parsed.beta(), Some("453"));
+ }
+
+ #[test]
+ fn test_parse_with_dev() {
+ let version = "2021.34-dev-0b60e4d87";
+ let parsed = Version::parse(version);
+ assert_eq!(parsed.year, "21");
+ assert_eq!(parsed.incremental, "34");
+ assert!(!parsed.is_stable());
+ assert_eq!(parsed.dev, Some("0b60e4d87".to_string()));
+ assert_eq!(parsed.alpha(), None);
+ assert_eq!(parsed.beta(), None);
+ }
+
+ #[test]
+ fn test_parse_both_beta_and_dev() {
+ let version = "2024.8-beta1-dev-e5483d";
+ let parsed = Version::parse(version);
+ assert_eq!(parsed.year, "24");
+ assert_eq!(parsed.incremental, "8");
+ assert_eq!(parsed.alpha(), None);
+ assert_eq!(parsed.beta(), Some("1"));
+ assert_eq!(parsed.dev, Some("e5483d".to_string()));
+ assert!(!parsed.is_stable());
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_panics_on_invalid_version() {
+ Version::parse("2021");
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_panics_on_invalid_version_type_number() {
+ Version::parse("2021.1-beta001");
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_panics_on_alpha_and_beta_in_same_version() {
+ Version::parse("2021.1-beta5-alpha2");
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_panics_on_dev_without_commit_hash() {
+ Version::parse("2021.1-dev");
+ }
+}
diff --git a/mullvad-version/src/main.rs b/mullvad-version/src/main.rs
index ef72286eb7..2e83cf24ee 100644
--- a/mullvad-version/src/main.rs
+++ b/mullvad-version/src/main.rs
@@ -1,4 +1,4 @@
-use mullvad_version::Version;
+use mullvad_version::{PreStableType, Version};
use std::{env, process::exit};
const ANDROID_VERSION: &str =
@@ -35,42 +35,98 @@ fn to_semver(version: &str) -> String {
/// Takes a version in the normal Mullvad VPN app version format and returns the Android
/// `versionCode` formatted version.
///
-/// The format of the code is: YYVV00XX
-/// Last two digits of the year (major) ^^
-/// Incrementing version (minor) ^^
-/// Unused ^^
-/// Beta number, 99 if stable ^^
+/// The format of the code is: YYVVXZZZ
+/// Last two digits of the year (major)---------^^
+/// Incrementing version (minor)------------------^^
+/// Build type (0=alpha, 1=beta, 9=stable/dev)------^
+/// Build number (000 if stable/dev)-----------------^^^
///
/// # Examples
///
+/// Version: 2021.1-alpha1
+/// versionCode: 21010001
+///
/// Version: 2021.34-beta5
-/// versionCode: 21340005
+/// versionCode: 21341005
///
/// Version: 2021.34
-/// versionCode: 21340099
+/// versionCode: 21349000
+///
+/// Version: 2021.34-dev
+/// versionCode: 21349000
fn to_android_version_code(version: &str) -> String {
- const ANDROID_STABLE_VERSION_CODE_SUFFIX: &str = "99";
-
let version = Version::parse(version);
+
+ let (build_type, build_number) = if version.dev.is_some() {
+ ("9", "000")
+ } else {
+ match &version.pre_stable {
+ Some(PreStableType::Alpha(v)) => ("0", v.as_str()),
+ Some(PreStableType::Beta(v)) => ("1", v.as_str()),
+ // Stable version
+ None => ("9", "000"),
+ }
+ };
+
format!(
- "{}{:0>2}00{:0>2}",
- version.year,
- version.incremental,
- version
- .beta
- .unwrap_or(ANDROID_STABLE_VERSION_CODE_SUFFIX.to_string())
+ "{}{:0>2}{}{:0>3}",
+ version.year, version.incremental, build_type, build_number,
)
}
-fn to_windows_h_format(version: &str) -> String {
+fn to_windows_h_format(version_str: &str) -> String {
+ let version = Version::parse(version_str);
+ assert!(
+ is_valid_windows_version(&version),
+ "Invalid Windows version: {version:?}"
+ );
+
let Version {
year, incremental, ..
- } = Version::parse(version);
+ } = version;
format!(
"#define MAJOR_VERSION 20{year}
#define MINOR_VERSION {incremental}
#define PATCH_VERSION 0
-#define PRODUCT_VERSION \"{version}\""
+#define PRODUCT_VERSION \"{version_str}\""
)
}
+
+/// On Windows we currently support the following versions: stable, beta and dev.
+fn is_valid_windows_version(version: &Version) -> bool {
+ version.is_stable()
+ || version.beta().is_some()
+ || (version.dev.is_some() && version.alpha().is_none())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_version_code() {
+ assert_eq!("21349000", to_android_version_code("2021.34"));
+ }
+
+ #[test]
+ fn test_version_code_alpha() {
+ assert_eq!("21010001", to_android_version_code("2021.1-alpha1"));
+ }
+
+ #[test]
+ fn test_version_code_beta() {
+ assert_eq!("21341005", to_android_version_code("2021.34-beta5"));
+ }
+
+ #[test]
+ fn test_version_code_dev() {
+ assert_eq!("21349000", to_android_version_code("2021.34-dev-be846a5f0"));
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_invalid_windows_version_code() {
+ to_windows_h_format("2021.34-alpha1");
+ }
+}
diff --git a/prepare-release.sh b/prepare-release.sh
index 21627daec5..7e6b443569 100755
--- a/prepare-release.sh
+++ b/prepare-release.sh
@@ -57,7 +57,10 @@ if [[ "$DESKTOP" == "true" && $(grep "^## \\[$PRODUCT_VERSION\\] - " CHANGELOG.m
exit 1
fi
-if [[ "$ANDROID" == "true" && $(grep "^## \\[android/$PRODUCT_VERSION\\] - " android/CHANGELOG.md) == "" ]]; then
+if [[ "$ANDROID" == "true" &&
+ $PRODUCT_VERSION != *"alpha"* &&
+ $(grep "^## \\[android/$PRODUCT_VERSION\\] - " android/CHANGELOG.md) == "" ]]; then
+
echo "It looks like you did not add $PRODUCT_VERSION to the changelog?"
echo "Please make sure the changelog is up to date and correct before you proceed."
exit 1