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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
use anyhow::{bail, format_err, Result};
use platform_info::*;
use platforms::Platform;
use serde::{Deserialize, Serialize};
use std::env;
use std::fmt;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;

/// All supported operating system
#[derive(Debug, Clone, Eq, PartialEq)]
enum OS {
    Linux,
    Windows,
    Macos,
    FreeBSD,
}

/// Decides how to handle manylinux compliance
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub enum Manylinux {
    /// Use manylinux2010 tag and check for compliance
    Manylinux2010,
    /// Use the manylinux2010 tag but don't check for compliance
    Manylinux2010Unchecked,
    /// Use manylinux2014 tag and check for compliance
    Manylinux2014,
    /// Use the manylinux2014 tag but don't check for compliance
    Manylinux2014Unchecked,
    /// Use the native linux tag
    Off,
}

impl fmt::Display for Manylinux {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Manylinux::Manylinux2010 => write!(f, "manylinux2010"),
            Manylinux::Manylinux2010Unchecked => write!(f, "manylinux2010"),
            Manylinux::Manylinux2014 => write!(f, "manylinux2014"),
            Manylinux::Manylinux2014Unchecked => write!(f, "manylinux2014"),
            Manylinux::Off => write!(f, "linux"),
        }
    }
}

impl FromStr for Manylinux {
    type Err = &'static str;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "2010" => Ok(Manylinux::Manylinux2010),
            "2010-unchecked" => Ok(Manylinux::Manylinux2010Unchecked),
            "2014" => Ok(Manylinux::Manylinux2014Unchecked),
            "2014-unchecked" => Ok(Manylinux::Manylinux2014Unchecked),
            "off" => Ok(Manylinux::Off),
            _ => Err("Invalid value for the manylinux option"),
        }
    }
}

/// All supported CPU architectures
#[derive(Debug, Clone, Eq, PartialEq)]
enum Arch {
    AARCH64,
    ARMV7L,
    X86,
    X86_64,
}

impl fmt::Display for Arch {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Arch::AARCH64 => write!(f, "aarch64"),
            Arch::ARMV7L => write!(f, "armv7l"),
            Arch::X86 => write!(f, "i686"),
            Arch::X86_64 => write!(f, "x86_64"),
        }
    }
}

/// The part of the current platform that is relevant when building wheels and is supported
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Target {
    os: OS,
    arch: Arch,
}

impl Target {
    /// Uses the given target triple or tries the guess the current target by using the one used
    /// for compilation
    ///
    /// Fails if the target triple isn't supported
    pub fn from_target_triple(target_triple: Option<String>) -> Result<Self> {
        let platform = if let Some(ref target_triple) = target_triple {
            Platform::find(target_triple)
                .ok_or_else(|| format_err!("Unknown target triple {}", target_triple))?
        } else {
            Platform::guess_current()
                .ok_or_else(|| format_err!("Could guess the current platform"))?
        };

        let os = match platform.target_os {
            platforms::target::OS::Linux => OS::Linux,
            platforms::target::OS::Windows => OS::Windows,
            platforms::target::OS::MacOS => OS::Macos,
            platforms::target::OS::FreeBSD => OS::FreeBSD,
            unsupported => bail!("The operating system {:?} is not supported", unsupported),
        };

        let arch = match platform.target_arch {
            platforms::target::Arch::X86_64 => Arch::X86_64,
            platforms::target::Arch::X86 => Arch::X86,
            platforms::target::Arch::ARM => Arch::ARMV7L,
            platforms::target::Arch::AARCH64 => Arch::AARCH64,
            unsupported => bail!("The architecture {:?} is not supported", unsupported),
        };

        // bail on any unsupported targets
        match (&os, &arch) {
            (OS::FreeBSD, Arch::AARCH64) => bail!("aarch64 is not supported for FreeBSD"),
            (OS::FreeBSD, Arch::ARMV7L) => bail!("armv7l is not supported for FreeBSD"),
            (OS::FreeBSD, Arch::X86) => bail!("32-bit wheels are not supported for FreeBSD"),
            (OS::FreeBSD, Arch::X86_64) => {
                match PlatformInfo::new() {
                    Ok(_) => {}
                    Err(error) => bail!(error),
                };
            }
            (OS::Macos, Arch::AARCH64) => bail!("aarch64 is not supported for macOS"),
            (OS::Macos, Arch::ARMV7L) => bail!("armv7l is not supported for macOS"),
            (OS::Macos, Arch::X86) => bail!("32-bit wheels are not supported for macOS"),
            (OS::Windows, Arch::AARCH64) => bail!("aarch64 is not supported for Windows"),
            (OS::Windows, Arch::ARMV7L) => bail!("armv7l is not supported for Windows"),
            (_, _) => {}
        }
        Ok(Target { os, arch })
    }

    /// Returns whether the platform is 64 bit or 32 bit
    pub fn pointer_width(&self) -> usize {
        match self.arch {
            Arch::AARCH64 => 64,
            Arch::ARMV7L => 32,
            Arch::X86 => 32,
            Arch::X86_64 => 64,
        }
    }

    /// Returns true if the current platform is linux or mac os
    pub fn is_unix(&self) -> bool {
        self.os != OS::Windows
    }

    /// Returns true if the current platform is linux
    pub fn is_linux(&self) -> bool {
        self.os == OS::Linux
    }

    /// Returns true if the current platform is freebsd
    pub fn is_freebsd(&self) -> bool {
        self.os == OS::FreeBSD
    }

    /// Returns true if the current platform is mac os
    pub fn is_macos(&self) -> bool {
        self.os == OS::Macos
    }

    /// Returns true if the current platform is windows
    pub fn is_windows(&self) -> bool {
        self.os == OS::Windows
    }

    /// Returns the platform part of the tag for the wheel name for cffi wheels
    pub fn get_platform_tag(&self, manylinux: &Manylinux) -> String {
        match (&self.os, &self.arch) {
            (OS::FreeBSD, Arch::X86_64) => {
                let info = match PlatformInfo::new() {
                    Ok(info) => info,
                    Err(error) => panic!(error),
                };
                let release = info.release().replace(".", "_").replace("-", "_");
                format!("freebsd_{}_amd64", release)
            }
            (OS::Linux, _) => format!("{}_{}", manylinux, self.arch),
            (OS::Macos, Arch::X86_64) => "macosx_10_7_x86_64".to_string(),
            (OS::Windows, Arch::X86) => "win32".to_string(),
            (OS::Windows, Arch::X86_64) => "win_amd64".to_string(),
            (_, _) => panic!("unsupported target should not have reached get_platform_tag()"),
        }
    }

    /// Returns the tags for the WHEEL file for cffi wheels
    pub fn get_py3_tags(&self, manylinux: &Manylinux) -> Vec<String> {
        vec![format!("py3-none-{}", self.get_platform_tag(&manylinux))]
    }

    /// Returns the platform for the tag in the shared libaries file name
    pub fn get_shared_platform_tag(&self) -> &'static str {
        match (&self.os, &self.arch) {
            (OS::FreeBSD, _) => "", // according imp.get_suffixes(), there are no such
            (OS::Linux, Arch::AARCH64) => "aarch64-linux-gnu", // aka armv8-linux-gnueabihf
            (OS::Linux, Arch::ARMV7L) => "arm-linux-gnueabihf",
            (OS::Linux, Arch::X86) => "i386-linux-gnu", // not i686
            (OS::Linux, Arch::X86_64) => "x86_64-linux-gnu",
            (OS::Macos, Arch::X86_64) => "darwin",
            (OS::Windows, Arch::X86) => "win32",
            (OS::Windows, Arch::X86_64) => "win_amd64",
            (OS::Macos, _) => {
                panic!("unsupported macOS Arch should not have reached get_shared_platform_tag()")
            }
            (OS::Windows, _) => {
                panic!("unsupported Windows Arch should not have reached get_shared_platform_tag()")
            }
        }
    }

    /// Returns the path to the python executable inside a venv
    pub fn get_venv_python(&self, venv_base: impl AsRef<Path>) -> PathBuf {
        if self.is_windows() {
            let path = venv_base.as_ref().join("Scripts").join("python.exe");
            if path.exists() {
                path
            } else {
                // for conda environment
                venv_base.as_ref().join("python.exe")
            }
        } else {
            venv_base.as_ref().join("bin").join("python")
        }
    }

    /// Returns the directory where the binaries are stored inside a venv
    pub fn get_venv_bin_dir(&self, venv_base: impl AsRef<Path>) -> PathBuf {
        if self.is_windows() {
            venv_base.as_ref().join("Scripts")
        } else {
            venv_base.as_ref().join("bin")
        }
    }

    /// Returns the path to the python executable
    ///
    /// For windows it's always python.exe for unix it's first the venv's `python`
    /// and then `python3`
    pub fn get_python(&self) -> PathBuf {
        if self.is_windows() {
            PathBuf::from("python.exe")
        } else if env::var_os("VIRTUAL_ENV").is_some() {
            PathBuf::from("python")
        } else {
            PathBuf::from("python3")
        }
    }

    /// Returns the tags for the platform without python version
    pub fn get_universal_tags(&self, manylinux: &Manylinux) -> (String, Vec<String>) {
        let tag = format!(
            "py3-none-{platform}",
            platform = self.get_platform_tag(&manylinux)
        );
        let tags = self.get_py3_tags(&manylinux);
        (tag, tags)
    }
}