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
//! Contains `Error`, `AndroidError`, `AppleError` types used by `crossbundle-tools`.

#[cfg(feature = "apple")]
use apple_bundle::plist;
use displaydoc::Display;
use std::path::PathBuf;
use std::process::Command;
use thiserror::Error;

/// `Result` type that used in `crossbundle-tools`.
pub type Result<T> = std::result::Result<T, Error>;

/// Android specific error type.
#[cfg(feature = "android")]
#[derive(Display, Debug, Error)]
pub enum AndroidError {
    /// Android NDK is not found
    AndroidNdkNotFound,
    /// Failed to read source.properties
    FailedToReadSourceProperties,
    /// Invalid source.properties: {0}
    InvalidSourceProperties(String),
    /// Gradle Dependency project dir not found: {0}
    GradleDependencyProjectNotFound(PathBuf),
    /// Gradle Dependency project doesn't contain build.gradle: {0}
    GradleDependencyProjectNoBuildFile(PathBuf),
    /// Gradle is not found
    GradleNotFound,
    /// Android SDK has no build tools
    BuildToolsNotFound,
    /// Android SDK has no platforms installed
    NoPlatformsFound,
    /// Platform {0} is not installed
    PlatformNotFound(u32),
    /// Target is not supported
    UnsupportedTarget,
    /// Host {0} is not supported
    UnsupportedHost(String),
    /// Invalid semver
    InvalidSemver,
    /// Unsupported or invalid target: {0}
    InvalidBuildTarget(String),
    /// Unsupported or invalid app wrapper: {0}
    InvalidAppWrapper(String),
    /// Unsupported or invalid build strategy: {0}
    InvalidBuildStrategy(String),
    /// Failed to find AndroidManifest.xml in path: {0}
    FailedToFindAndroidManifest(String),
    /// Unable to find NDK file
    UnableToFindNDKFile,
    /// AndroidTools error: {0:?}
    AndroidTools(#[from] android_tools::error::Error),
    /// AndroidManifest error: {0:?}
    AndroidManifest(#[from] android_manifest::error::Error),
}

/// Apple specific error type.
#[cfg(feature = "apple")]
#[derive(Display, Debug, Error)]
pub enum AppleError {
    /// Code signing profile not found
    CodeSigningProfilesNotFound,
    /// Code signing profile not provided
    CodeSigningProfileNotProvided,
    /// Codesign failed {0}
    CodesignFailed(String),
    /// Failed to archive payload
    ZipCommandFailed,
    /// Codesign allocate not found
    CodesignAllocateNotFound,
    /// Simctl error: {0:?}
    Simctl(simctl::Error),
    /// Target dir does not exists
    TargetNotFound,
    /// Resources dir does not exists
    ResourcesNotFound,
    /// Unsupported or invalid build strategy: {0}
    InvalidBuildStrategy(String),
    /// Unsupported or invalid target: {0}
    InvalidBuildTarget(String),
    /// Assets dir does not exists
    AssetsNotFound,
    /// Failed to find Info.plist in path: {0}
    FailedToFindInfoPlist(String),
    /// Plist data error: {0:?}
    Plist(#[from] plist::Error),
}

/// Main error type.
#[derive(Display, Debug, Error)]
#[ignore_extra_doc_attributes]
pub enum Error {
    /// Command '{0:?}' had a non-zero exit code. Stdout: {1} Stderr: {2}
    CmdFailed(Command, String, String),
    /// Command {0} not found
    CmdNotFound(String),
    /// Failed to copy file in specified path `{path}` cause of `{cause}`
    CopyToFileFailed {
        path: PathBuf,
        cause: std::io::Error,
    },
    /// Failed to find the manifest in path: {0}
    FailedToFindManifest(PathBuf),
    /// Invalid profile: {0}
    InvalidProfile(String),
    /// GNU toolchain binary `{gnu_bin}` nor LLVM toolchain binary `{llvm_bin}` found in
    /// `{toolchain_path:?}`
    ToolchainBinaryNotFound {
        toolchain_path: PathBuf,
        gnu_bin: String,
        llvm_bin: String,
    },
    /// Path {0:?} doesn't exist
    PathNotFound(PathBuf),
    /// Failed to find cargo manifest: {0}
    FailedToFindCargoManifest(String),
    /// Failed to choose shell string color.
    /// Argument for --color must be auto, always, or never, but found `{}`
    FailedToChooseShellStringColor(String),
    /// IO error: {0:?}
    Io(#[from] std::io::Error),
    /// FS Extra error: {0:?}
    FsExtra(#[from] fs_extra::error::Error),
    /// Zip error: {0:?}
    Zip(#[from] zip::result::ZipError),
    /// Android error: {0:?}
    #[cfg(feature = "android")]
    Android(#[from] AndroidError),
    /// Apple error: {0:?}
    #[cfg(feature = "apple")]
    Apple(#[from] AppleError),
    /// Anyhow error: {0:?}
    AnyhowError(#[from] anyhow::Error),
    /// Other error: {0:?}
    OtherError(#[from] Box<dyn std::error::Error>),
}

/// Extension trait for [`Command`] that helps
/// to wrap output and print logs from command execution.
///
/// [`Command`]: std::process::Command
pub trait CommandExt {
    /// Executes the command as a child process, then captures an output and return it.
    /// If command termination wasn't successful wraps an output into error and return it.
    fn output_err(self, print_logs: bool) -> Result<std::process::Output>;
}

impl CommandExt for Command {
    fn output_err(mut self, print_logs: bool) -> Result<std::process::Output> {
        // Enables log print during command execution
        let output = match print_logs {
            true => self.spawn().and_then(|p| p.wait_with_output())?,
            false => self.output()?,
        };
        if !output.status.success() {
            return Err(Error::CmdFailed(
                self,
                String::from_utf8_lossy(&output.stdout).to_string(),
                String::from_utf8_lossy(&output.stderr).to_string(),
            ));
        }
        Ok(output)
    }
}

#[cfg(feature = "apple")]
impl From<plist::Error> for Error {
    fn from(error: plist::Error) -> Self {
        AppleError::from(error).into()
    }
}

#[cfg(feature = "apple")]
impl From<simctl::Error> for Error {
    fn from(error: simctl::Error) -> Self {
        AppleError::Simctl(error).into()
    }
}

#[cfg(feature = "android")]
impl From<android_tools::error::Error> for Error {
    fn from(error: android_tools::error::Error) -> Self {
        AndroidError::AndroidTools(error).into()
    }
}