summaryrefslogtreecommitdiffhomepage
path: root/mullvad-version/src/main.rs
blob: a4892633c9a66477d3483cc600f88815b0c6f761 (plain)
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,
    }
}