* Copyright (c) 2024 Huawei Technologies Co., Ltd.
* upatch-build is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
use std::{
env,
ffi::{OsStr, OsString},
fs::File,
io::Write,
os::unix::ffi::{OsStrExt as _, OsStringExt},
path::{Path, PathBuf},
};
use anyhow::{Context, Result};
use indexmap::IndexMap;
use log::{debug, Level};
use which::which;
use crate::{args::Arguments, build_root::BuildRoot, compiler::Compiler, dwarf::ProducerType};
use syscare_common::{concat_os, ffi::OsStrExt, fs, process::Command};
const PATCH_BIN: &str = "patch";
const UPATCH_HELPER_BIN: &str = "upatch-helper";
const UPATCH_HELPER_CC_BIN: &str = "upatch-cc";
const UPATCH_HELPER_CXX_BIN: &str = "upatch-c++";
const PREPARE_SCRIPT_NAME: &str = "prepare.sh";
const BUILD_SCRIPT_NAME: &str = "build.sh";
const CLEAN_SCRIPT_NAME: &str = "clean.sh";
const PATH_ENV: &str = "PATH";
const CC_ENV: &str = "CC";
const CXX_ENV: &str = "CXX";
const UPATCH_CC_ENV: &str = "UPATCH_HELPER_CC";
const UPATCH_CXX_ENV: &str = "UPATCH_HELPER_CXX";
const CC_VERSION_ENV: &str = "CC_VERSION";
const CXX_VERSION_ENV: &str = "CXX_VERSION";
pub struct Project<'a> {
name: OsString,
build_root: &'a BuildRoot,
source_dir: &'a Path,
source_ext: &'a [OsString],
prepare_cmd: &'a OsStr,
build_cmd: &'a OsStr,
clean_cmd: &'a OsStr,
patches: &'a [PathBuf],
}
impl<'a> Project<'a> {
pub fn new(
args: &'a Arguments,
build_root: &'a BuildRoot,
compiler_map: &'a IndexMap<ProducerType, Compiler>,
) -> Result<Self> {
let path_env = env::var_os(PATH_ENV)
.with_context(|| format!("Cannot read environment variable '{}'", PATH_ENV))?;
let upatch_helper = which(UPATCH_HELPER_BIN)
.with_context(|| format!("Cannot find component '{}'", UPATCH_HELPER_BIN))?;
for (kind, compiler) in compiler_map {
let compiler_path = compiler.path.as_path();
let compiler_name = compiler_path
.file_name()
.context("Failed to parse compiler name")?;
match kind {
ProducerType::GnuC | ProducerType::ClangC => {
env::set_var(UPATCH_CC_ENV, compiler_path);
env::set_var(CC_VERSION_ENV, compiler.version.clone());
}
ProducerType::GnuCxx | ProducerType::ClangCxx => {
env::set_var(UPATCH_CXX_ENV, compiler_path);
env::set_var(CXX_VERSION_ENV, compiler.version.clone());
}
_ => {}
}
fs::soft_link(&upatch_helper, build_root.bin_dir.join(compiler_name))?;
}
env::set_var(PATH_ENV, concat_os!(&build_root.bin_dir, ":", path_env));
env::set_var(CC_ENV, UPATCH_HELPER_CC_BIN);
env::set_var(CXX_ENV, UPATCH_HELPER_CXX_BIN);
Ok(Self {
name: args
.source_dir
.file_name()
.context("Failed to parse project name")?
.to_os_string(),
build_root,
source_dir: args.source_dir.as_path(),
source_ext: args.source_ext.as_slice(),
prepare_cmd: args.prepare_cmd.as_os_str(),
build_cmd: args.build_cmd.as_os_str(),
clean_cmd: args.clean_cmd.as_os_str(),
patches: args.patch.as_slice(),
})
}
}
impl Project<'_> {
fn patch<P, I, S>(&self, patch_file: P, args: I) -> Result<()>
where
P: AsRef<Path>,
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
Command::new(PATCH_BIN)
.args(args)
.arg("-i")
.arg(patch_file.as_ref())
.current_dir(self.source_dir)
.run_with_output()?
.exit_ok()
}
fn create_script<S, T>(&self, script_name: S, command: T) -> Result<PathBuf>
where
S: AsRef<OsStr>,
T: AsRef<OsStr>,
{
let script = self.build_root.script_dir.join(script_name.as_ref());
let mut script_file = File::create(&script)?;
script_file.write_all(b"#!/bin/bash\n")?;
script_file.write_all(command.as_ref().as_bytes())?;
Ok(script)
}
fn exec_command<S, T>(&self, script_name: S, command: T) -> Result<()>
where
S: AsRef<OsStr>,
T: AsRef<OsStr>,
{
if command.as_ref().is_empty() {
return Ok(());
}
let script = self.create_script(script_name, command)?;
Command::new("sh")
.arg(script)
.current_dir(self.source_dir)
.stdout(Level::Debug)
.run_with_output()?
.exit_ok()
}
}
impl Project<'_> {
pub fn apply_patches(&self) -> Result<()> {
for patch in self.patches.iter() {
debug!("* {}", patch.display());
self.patch(patch, ["-N", "-p1"])
.with_context(|| format!("Failed to patch {}", patch.display()))?;
}
Ok(())
}
pub fn remove_patches(&self) -> Result<()> {
for patch in self.patches.iter().rev() {
self.patch(patch, ["-R", "-p1"])
.with_context(|| format!("Failed to unpatch {}", patch.display()))?;
}
Ok(())
}
pub fn test_patches(&self) -> Result<()> {
self.apply_patches()?;
self.remove_patches()?;
Ok(())
}
pub fn restore_backup_files(&self, backup_files: &[PathBuf]) -> Result<()> {
for file_path in backup_files {
let backup_path = PathBuf::from(concat_os!(file_path, ".bak"));
if backup_path.exists() {
fs::rename(&backup_path, file_path)?;
debug!("Restored: {}", backup_path.to_string_lossy());
}
}
Ok(())
}
pub fn override_line_macros(&self) -> Result<Vec<PathBuf>> {
const LINE_MACRO_NAME: &str = "__LINE__";
const LINE_MACRO_VALUE: &str = "0";
let mut backup_files = Vec::new();
let file_list = fs::list_files(self.source_dir, fs::TraverseOptions { recursive: true })?;
for file_path in file_list {
let file_ext = file_path.extension().unwrap_or_default();
if self.source_ext.iter().all(|ext| ext != file_ext) {
continue;
}
let old_contents = fs::read(&file_path)?;
let new_contents = OsStr::from_bytes(&old_contents)
.replace(LINE_MACRO_NAME, LINE_MACRO_VALUE)
.into_vec();
if old_contents != new_contents {
let backup_path = PathBuf::from(concat_os!(&file_path, ".bak"));
fs::copy(&file_path, &backup_path)?;
backup_files.push(file_path.clone());
debug!("* {}", file_path.display());
fs::write(&file_path, new_contents)?;
}
}
Ok(backup_files)
}
pub fn prepare(&self) -> Result<()> {
self.exec_command(PREPARE_SCRIPT_NAME, self.prepare_cmd)
}
pub fn build(&self) -> Result<()> {
self.exec_command(BUILD_SCRIPT_NAME, self.build_cmd)
}
pub fn clean(&self) -> Result<()> {
self.exec_command(CLEAN_SCRIPT_NAME, self.clean_cmd)
}
}
impl std::fmt::Display for Project<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name.to_string_lossy())
}
}
impl Drop for Project<'_> {
fn drop(&mut self) {
self.remove_patches().ok();
}
}