summaryrefslogtreecommitdiffhomepage
path: root/mullvad-version/src/main.rs
blob: 68906e436007498dffb27279dfc15f032a6bf167 (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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#[cfg(has_version)]
mod inner {
    use mullvad_version::{PreStableType, Version};
    use std::env::VarError;
    use std::{env, process::exit};

    const ANDROID_VERSION: &str =
        include_str!(concat!(env!("OUT_DIR"), "/android-version-name.txt"));

    pub fn main() {
        let android_version_env = env::var("ANDROID_VERSION");
        if matches!(android_version_env, Err(VarError::NotUnicode(_))) {
            eprintln!("ANDROID_VERSION is not valid unicode.");
            exit(1);
        }
        let android_version = android_version_env.unwrap_or(ANDROID_VERSION.to_string());

        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:                    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: 21341005
    ///
    /// Version: 2021.34
    /// versionCode: 21349000
    ///
    /// Version: 2021.34-dev
    /// versionCode: 21349000
    fn to_android_version_code(version: &str) -> String {
        let version: Version = version.parse().unwrap();

        let (build_type, build_number) = if version.dev.is_some() {
            ("9", "000".to_string())
        } else {
            match &version.pre_stable {
                Some(PreStableType::Alpha(v)) => ("0", v.to_string()),
                Some(PreStableType::Beta(v)) => ("1", v.to_string()),
                // Stable version
                None => ("9", "000".to_string()),
            }
        };

        let year_last_two_digits = version.year % 100;

        format!(
            "{}{:0>2}{}{:0>3}",
            year_last_two_digits, version.incremental, build_type, build_number,
        )
    }

    fn to_windows_h_format(version_str: &str) -> String {
        let version = version_str.parse().unwrap();

        let Version {
            year, incremental, ..
        } = version;

        format!(
            "#define MAJOR_VERSION {year}
    #define MINOR_VERSION {incremental}
    #define PATCH_VERSION 0
    #define PRODUCT_VERSION \"{version_str}\""
        )
    }

    #[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]
        fn test_windows_version_h() {
            let version_h = to_windows_h_format("2025.4-beta2-dev-abcdef");
            let expected_version_h = "#define MAJOR_VERSION 2025
    #define MINOR_VERSION 4
    #define PATCH_VERSION 0
    #define PRODUCT_VERSION \"2025.4-beta2-dev-abcdef\"";
            assert_eq!(expected_version_h, version_h);
        }
    }
}

#[cfg(not(has_version))]
mod inner {
    pub fn main() {}
}

fn main() {
    inner::main();
}