phper_test/
cargo.rs

1// Copyright (c) 2022 PHPER Framework Team
2// PHPER is licensed under Mulan PSL v2.
3// You can use this software according to the terms and conditions of the Mulan
4// PSL v2. You may obtain a copy of Mulan PSL v2 at:
5//          http://license.coscl.org.cn/MulanPSL2
6// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
7// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
8// NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
9// See the Mulan PSL v2 for more details.
10
11//! Cargo build utilities for building and analyzing Rust libraries.
12
13use cargo_metadata::Message;
14use log::{debug, trace};
15use std::{
16    io::{self, BufReader},
17    path::{Path, PathBuf},
18    process::{Command, Stdio},
19};
20
21/// Builder for running cargo build commands with JSON output
22pub struct CargoBuilder {
23    command: Command,
24}
25
26/// Result of a cargo build operation
27pub struct CargoBuildResult {
28    messages: Vec<Message>,
29}
30
31impl CargoBuilder {
32    /// Create a new CargoBuilder instance
33    pub fn new() -> Self {
34        let mut args = vec!["build", "--lib", "--message-format", "json"];
35        if !cfg!(debug_assertions) {
36            args.push("--release");
37        }
38        let mut command = Command::new(env!("CARGO"));
39        command
40            .args(&args)
41            .stdin(Stdio::null())
42            .stdout(Stdio::piped())
43            .stderr(Stdio::null());
44        Self { command }
45    }
46
47    /// Add additional arguments to the cargo command
48    pub fn arg<S: AsRef<std::ffi::OsStr>>(&mut self, arg: S) -> &mut Self {
49        self.command.arg(arg);
50        self
51    }
52
53    /// Set the current directory for the cargo command
54    pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
55        self.command.current_dir(dir);
56        self
57    }
58
59    /// Execute the cargo build command and return the result
60    pub fn build(&mut self) -> io::Result<CargoBuildResult> {
61        debug!(command:% = {
62            let program = self.command.get_program();
63            let args = self.command.get_args();
64            let mut command = vec![program];
65            command.extend(args);
66            command.join(" ".as_ref()).to_string_lossy().to_string()
67        }; "run cargo build command");
68
69        let mut child = self.command.spawn()?;
70        let stdout = child
71            .stdout
72            .take()
73            .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Failed to capture stdout"))?;
74        let reader = BufReader::new(stdout);
75        let mut messages = Vec::new();
76        for message in cargo_metadata::Message::parse_stream(reader) {
77            trace!(message:?; "cargo build message");
78            let message = message?;
79            messages.push(message);
80        }
81        let exit_status = child.wait()?;
82        if !exit_status.success() {
83            return Err(io::Error::new(
84                io::ErrorKind::Other,
85                format!("Cargo build failed with exit status: {}", exit_status),
86            ));
87        }
88        Ok(CargoBuildResult { messages })
89    }
90}
91
92impl Default for CargoBuilder {
93    fn default() -> Self {
94        Self::new()
95    }
96}
97
98impl CargoBuildResult {
99    /// Get the cdylib file path from the last compiler-artifact message
100    pub fn get_cdylib(&self) -> Option<PathBuf> {
101        self.messages.iter().rev().find_map(|msg| {
102            if let Message::CompilerArtifact(artifact) = msg {
103                artifact.filenames.iter().find_map(|filename| {
104                    let ext = filename.extension();
105                    if matches!(ext, Some("so") | Some("dylib") | Some("dll")) {
106                        Some(PathBuf::from(filename.as_std_path()))
107                    } else {
108                        None
109                    }
110                })
111            } else {
112                None
113            }
114        })
115    }
116}