1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
use regex::Regex;
use std::{env, process::exit};
const ANDROID_VERSION: &str =
include_str!(concat!(env!("OUT_DIR"), "/android-product-version.txt"));
const VERSION_REGEX: &str = r"^20([0-9]{2})\.([1-9][0-9]?)(-beta([1-9][0-9]?))?(-dev-[0-9a-f]+)?$";
const ANDROID_STABLE_VERSION_CODE_SUFFIX: &str = "99";
fn main() {
let command = env::args().nth(1);
match command.as_deref() {
None => println!("{}", mullvad_version::VERSION),
Some("semver") => println!("{}", to_semver(mullvad_version::VERSION)),
Some("version.h") => println!("{}", to_windows_h_format(mullvad_version::VERSION)),
Some("versionName") => println!("{ANDROID_VERSION}"),
Some("versionCode") => println!("{}", to_android_version_code(ANDROID_VERSION)),
Some(command) => {
eprintln!("Unknown command: {command}");
exit(1);
}
}
}
/// Takes a version without a patch number and adds the patch (set to zero).
///
/// Converts `x.y[-z]` into `x.y.0[-z]` to make the version semver compatible.
fn to_semver(version: &str) -> String {
let mut parts = version.splitn(2, '-');
let version = parts.next().expect("Year component");
let remainder = parts.next().map(|s| format!("-{s}")).unwrap_or_default();
assert_eq!(parts.next(), None);
format!("{version}.0{remainder}")
}
/// 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 ^^
///
/// # Examples
///
/// Version: 2021.34-beta5
/// versionCode: 21340005
///
/// Version: 2021.34
/// versionCode: 21340099
fn to_android_version_code(version: &str) -> String {
let version = parse_version(version);
format!(
"{}{:0>2}00{:0>2}",
version.year,
version.incremental,
version
.beta
.unwrap_or(ANDROID_STABLE_VERSION_CODE_SUFFIX.to_string())
)
}
fn to_windows_h_format(version: &str) -> String {
let Version {
year, incremental, ..
} = parse_version(version);
format!(
"#define MAJOR_VERSION 20{year}
#define MINOR_VERSION {incremental}
#define PATCH_VERSION 0
#define PRODUCT_VERSION \"{version}\""
)
}
struct Version {
year: String,
incremental: String,
beta: Option<String>,
}
fn parse_version(version: &str) -> Version {
let re = Regex::new(VERSION_REGEX).unwrap();
let captures = re
.captures(version)
.expect("Version does not match expected format");
let year = captures.get(1).expect("Missing year").as_str().to_owned();
let incremental = captures
.get(2)
.expect("Missing incremental")
.as_str()
.to_owned();
let beta = captures.get(4).map(|m| m.as_str().to_owned());
Version {
year,
incremental,
beta,
}
}
|