16cdb10c1Sopenharmony_ci// SPDX-License-Identifier: Apache-2.0
26cdb10c1Sopenharmony_ci
36cdb10c1Sopenharmony_ciextern crate glob;
46cdb10c1Sopenharmony_ci
56cdb10c1Sopenharmony_ciuse std::cell::RefCell;
66cdb10c1Sopenharmony_ciuse std::collections::HashMap;
76cdb10c1Sopenharmony_ciuse std::env;
86cdb10c1Sopenharmony_ciuse std::path::{Path, PathBuf};
96cdb10c1Sopenharmony_ciuse std::process::Command;
106cdb10c1Sopenharmony_ci
116cdb10c1Sopenharmony_ciuse glob::{MatchOptions, Pattern};
126cdb10c1Sopenharmony_ci
136cdb10c1Sopenharmony_ci//================================================
146cdb10c1Sopenharmony_ci// Commands
156cdb10c1Sopenharmony_ci//================================================
166cdb10c1Sopenharmony_ci
176cdb10c1Sopenharmony_cithread_local! {
186cdb10c1Sopenharmony_ci    /// The errors encountered by the build script while executing commands.
196cdb10c1Sopenharmony_ci    static COMMAND_ERRORS: RefCell<HashMap<String, Vec<String>>> = RefCell::default();
206cdb10c1Sopenharmony_ci}
216cdb10c1Sopenharmony_ci
226cdb10c1Sopenharmony_ci/// Adds an error encountered by the build script while executing a command.
236cdb10c1Sopenharmony_cifn add_command_error(name: &str, path: &str, arguments: &[&str], message: String) {
246cdb10c1Sopenharmony_ci    COMMAND_ERRORS.with(|e| {
256cdb10c1Sopenharmony_ci        e.borrow_mut()
266cdb10c1Sopenharmony_ci            .entry(name.into())
276cdb10c1Sopenharmony_ci            .or_insert_with(Vec::new)
286cdb10c1Sopenharmony_ci            .push(format!(
296cdb10c1Sopenharmony_ci                "couldn't execute `{} {}` (path={}) ({})",
306cdb10c1Sopenharmony_ci                name,
316cdb10c1Sopenharmony_ci                arguments.join(" "),
326cdb10c1Sopenharmony_ci                path,
336cdb10c1Sopenharmony_ci                message,
346cdb10c1Sopenharmony_ci            ))
356cdb10c1Sopenharmony_ci    });
366cdb10c1Sopenharmony_ci}
376cdb10c1Sopenharmony_ci
386cdb10c1Sopenharmony_ci/// A struct that prints the errors encountered by the build script while
396cdb10c1Sopenharmony_ci/// executing commands when dropped (unless explictly discarded).
406cdb10c1Sopenharmony_ci///
416cdb10c1Sopenharmony_ci/// This is handy because we only want to print these errors when the build
426cdb10c1Sopenharmony_ci/// script fails to link to an instance of `libclang`. For example, if
436cdb10c1Sopenharmony_ci/// `llvm-config` couldn't be executed but an instance of `libclang` was found
446cdb10c1Sopenharmony_ci/// anyway we don't want to pollute the build output with irrelevant errors.
456cdb10c1Sopenharmony_ci#[derive(Default)]
466cdb10c1Sopenharmony_cipub struct CommandErrorPrinter {
476cdb10c1Sopenharmony_ci    discard: bool,
486cdb10c1Sopenharmony_ci}
496cdb10c1Sopenharmony_ci
506cdb10c1Sopenharmony_ciimpl CommandErrorPrinter {
516cdb10c1Sopenharmony_ci    pub fn discard(mut self) {
526cdb10c1Sopenharmony_ci        self.discard = true;
536cdb10c1Sopenharmony_ci    }
546cdb10c1Sopenharmony_ci}
556cdb10c1Sopenharmony_ci
566cdb10c1Sopenharmony_ciimpl Drop for CommandErrorPrinter {
576cdb10c1Sopenharmony_ci    fn drop(&mut self) {
586cdb10c1Sopenharmony_ci        if self.discard {
596cdb10c1Sopenharmony_ci            return;
606cdb10c1Sopenharmony_ci        }
616cdb10c1Sopenharmony_ci
626cdb10c1Sopenharmony_ci        let errors = COMMAND_ERRORS.with(|e| e.borrow().clone());
636cdb10c1Sopenharmony_ci
646cdb10c1Sopenharmony_ci        if let Some(errors) = errors.get("llvm-config") {
656cdb10c1Sopenharmony_ci            println!(
666cdb10c1Sopenharmony_ci                "cargo:warning=could not execute `llvm-config` one or more \
676cdb10c1Sopenharmony_ci                times, if the LLVM_CONFIG_PATH environment variable is set to \
686cdb10c1Sopenharmony_ci                a full path to valid `llvm-config` executable it will be used \
696cdb10c1Sopenharmony_ci                to try to find an instance of `libclang` on your system: {}",
706cdb10c1Sopenharmony_ci                errors
716cdb10c1Sopenharmony_ci                    .iter()
726cdb10c1Sopenharmony_ci                    .map(|e| format!("\"{}\"", e))
736cdb10c1Sopenharmony_ci                    .collect::<Vec<_>>()
746cdb10c1Sopenharmony_ci                    .join("\n  "),
756cdb10c1Sopenharmony_ci            )
766cdb10c1Sopenharmony_ci        }
776cdb10c1Sopenharmony_ci
786cdb10c1Sopenharmony_ci        if let Some(errors) = errors.get("xcode-select") {
796cdb10c1Sopenharmony_ci            println!(
806cdb10c1Sopenharmony_ci                "cargo:warning=could not execute `xcode-select` one or more \
816cdb10c1Sopenharmony_ci                times, if a valid instance of this executable is on your PATH \
826cdb10c1Sopenharmony_ci                it will be used to try to find an instance of `libclang` on \
836cdb10c1Sopenharmony_ci                your system: {}",
846cdb10c1Sopenharmony_ci                errors
856cdb10c1Sopenharmony_ci                    .iter()
866cdb10c1Sopenharmony_ci                    .map(|e| format!("\"{}\"", e))
876cdb10c1Sopenharmony_ci                    .collect::<Vec<_>>()
886cdb10c1Sopenharmony_ci                    .join("\n  "),
896cdb10c1Sopenharmony_ci            )
906cdb10c1Sopenharmony_ci        }
916cdb10c1Sopenharmony_ci    }
926cdb10c1Sopenharmony_ci}
936cdb10c1Sopenharmony_ci
946cdb10c1Sopenharmony_ci/// Executes a command and returns the `stdout` output if the command was
956cdb10c1Sopenharmony_ci/// successfully executed (errors are added to `COMMAND_ERRORS`).
966cdb10c1Sopenharmony_cifn run_command(name: &str, path: &str, arguments: &[&str]) -> Option<String> {
976cdb10c1Sopenharmony_ci    let output = match Command::new(path).args(arguments).output() {
986cdb10c1Sopenharmony_ci        Ok(output) => output,
996cdb10c1Sopenharmony_ci        Err(error) => {
1006cdb10c1Sopenharmony_ci            let message = format!("error: {}", error);
1016cdb10c1Sopenharmony_ci            add_command_error(name, path, arguments, message);
1026cdb10c1Sopenharmony_ci            return None;
1036cdb10c1Sopenharmony_ci        }
1046cdb10c1Sopenharmony_ci    };
1056cdb10c1Sopenharmony_ci
1066cdb10c1Sopenharmony_ci    if output.status.success() {
1076cdb10c1Sopenharmony_ci        Some(String::from_utf8_lossy(&output.stdout).into_owned())
1086cdb10c1Sopenharmony_ci    } else {
1096cdb10c1Sopenharmony_ci        let message = format!("exit code: {}", output.status);
1106cdb10c1Sopenharmony_ci        add_command_error(name, path, arguments, message);
1116cdb10c1Sopenharmony_ci        None
1126cdb10c1Sopenharmony_ci    }
1136cdb10c1Sopenharmony_ci}
1146cdb10c1Sopenharmony_ci
1156cdb10c1Sopenharmony_ci/// Executes the `llvm-config` command and returns the `stdout` output if the
1166cdb10c1Sopenharmony_ci/// command was successfully executed (errors are added to `COMMAND_ERRORS`).
1176cdb10c1Sopenharmony_cipub fn run_llvm_config(arguments: &[&str]) -> Option<String> {
1186cdb10c1Sopenharmony_ci    let path = env::var("LLVM_CONFIG_PATH").unwrap_or_else(|_| "llvm-config".into());
1196cdb10c1Sopenharmony_ci    run_command("llvm-config", &path, arguments)
1206cdb10c1Sopenharmony_ci}
1216cdb10c1Sopenharmony_ci
1226cdb10c1Sopenharmony_ci/// Executes the `xcode-select` command and returns the `stdout` output if the
1236cdb10c1Sopenharmony_ci/// command was successfully executed (errors are added to `COMMAND_ERRORS`).
1246cdb10c1Sopenharmony_cipub fn run_xcode_select(arguments: &[&str]) -> Option<String> {
1256cdb10c1Sopenharmony_ci    run_command("xcode-select", "xcode-select", arguments)
1266cdb10c1Sopenharmony_ci}
1276cdb10c1Sopenharmony_ci
1286cdb10c1Sopenharmony_ci//================================================
1296cdb10c1Sopenharmony_ci// Search Directories
1306cdb10c1Sopenharmony_ci//================================================
1316cdb10c1Sopenharmony_ci
1326cdb10c1Sopenharmony_ci/// `libclang` directory patterns for Haiku.
1336cdb10c1Sopenharmony_ciconst DIRECTORIES_HAIKU: &[&str] = &[
1346cdb10c1Sopenharmony_ci    "/boot/system/lib",
1356cdb10c1Sopenharmony_ci    "/boot/system/develop/lib",
1366cdb10c1Sopenharmony_ci    "/boot/system/non-packaged/lib",
1376cdb10c1Sopenharmony_ci    "/boot/system/non-packaged/develop/lib",
1386cdb10c1Sopenharmony_ci    "/boot/home/config/non-packaged/lib",
1396cdb10c1Sopenharmony_ci    "/boot/home/config/non-packaged/develop/lib",
1406cdb10c1Sopenharmony_ci];
1416cdb10c1Sopenharmony_ci
1426cdb10c1Sopenharmony_ci/// `libclang` directory patterns for Linux (and FreeBSD).
1436cdb10c1Sopenharmony_ciconst DIRECTORIES_LINUX: &[&str] = &[
1446cdb10c1Sopenharmony_ci    "/usr/lib*",
1456cdb10c1Sopenharmony_ci    "/usr/lib*/*",
1466cdb10c1Sopenharmony_ci    "/usr/lib*/*/*",
1476cdb10c1Sopenharmony_ci    "/usr/local/lib*",
1486cdb10c1Sopenharmony_ci    "/usr/local/lib*/*",
1496cdb10c1Sopenharmony_ci    "/usr/local/lib*/*/*",
1506cdb10c1Sopenharmony_ci    "/usr/local/llvm*/lib*",
1516cdb10c1Sopenharmony_ci];
1526cdb10c1Sopenharmony_ci
1536cdb10c1Sopenharmony_ci/// `libclang` directory patterns for macOS.
1546cdb10c1Sopenharmony_ciconst DIRECTORIES_MACOS: &[&str] = &[
1556cdb10c1Sopenharmony_ci    "/usr/local/opt/llvm*/lib",
1566cdb10c1Sopenharmony_ci    "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib",
1576cdb10c1Sopenharmony_ci    "/Library/Developer/CommandLineTools/usr/lib",
1586cdb10c1Sopenharmony_ci    "/usr/local/opt/llvm*/lib/llvm*/lib",
1596cdb10c1Sopenharmony_ci];
1606cdb10c1Sopenharmony_ci
1616cdb10c1Sopenharmony_ci/// `libclang` directory patterns for Windows.
1626cdb10c1Sopenharmony_ciconst DIRECTORIES_WINDOWS: &[&str] = &[
1636cdb10c1Sopenharmony_ci    "C:\\LLVM\\lib",
1646cdb10c1Sopenharmony_ci    "C:\\Program Files*\\LLVM\\lib",
1656cdb10c1Sopenharmony_ci    "C:\\MSYS*\\MinGW*\\lib",
1666cdb10c1Sopenharmony_ci    // LLVM + Clang can be installed as a component of Visual Studio.
1676cdb10c1Sopenharmony_ci    // https://github.com/KyleMayes/clang-sys/issues/121
1686cdb10c1Sopenharmony_ci    "C:\\Program Files*\\Microsoft Visual Studio\\*\\BuildTools\\VC\\Tools\\Llvm\\**\\bin",
1696cdb10c1Sopenharmony_ci    // LLVM + Clang can be installed using Scoop (https://scoop.sh).
1706cdb10c1Sopenharmony_ci    // Other Windows package managers install LLVM + Clang to previously listed
1716cdb10c1Sopenharmony_ci    // system-wide directories.
1726cdb10c1Sopenharmony_ci    "C:\\Users\\*\\scoop\\apps\\llvm\\current\\bin",
1736cdb10c1Sopenharmony_ci];
1746cdb10c1Sopenharmony_ci
1756cdb10c1Sopenharmony_ci/// `libclang` directory patterns for illumos
1766cdb10c1Sopenharmony_ciconst DIRECTORIES_ILLUMOS: &[&str] = &[
1776cdb10c1Sopenharmony_ci    "/opt/ooce/clang-*/lib",
1786cdb10c1Sopenharmony_ci    "/opt/ooce/llvm-*/lib",
1796cdb10c1Sopenharmony_ci];
1806cdb10c1Sopenharmony_ci
1816cdb10c1Sopenharmony_ci//================================================
1826cdb10c1Sopenharmony_ci// Searching
1836cdb10c1Sopenharmony_ci//================================================
1846cdb10c1Sopenharmony_ci
1856cdb10c1Sopenharmony_ci/// Finds the files in a directory that match one or more filename glob patterns
1866cdb10c1Sopenharmony_ci/// and returns the paths to and filenames of those files.
1876cdb10c1Sopenharmony_cifn search_directory(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
1886cdb10c1Sopenharmony_ci    // Escape the specified directory in case it contains characters that have
1896cdb10c1Sopenharmony_ci    // special meaning in glob patterns (e.g., `[` or `]`).
1906cdb10c1Sopenharmony_ci    let directory = Pattern::escape(directory.to_str().unwrap());
1916cdb10c1Sopenharmony_ci    let directory = Path::new(&directory);
1926cdb10c1Sopenharmony_ci
1936cdb10c1Sopenharmony_ci    // Join the escaped directory to the filename glob patterns to obtain
1946cdb10c1Sopenharmony_ci    // complete glob patterns for the files being searched for.
1956cdb10c1Sopenharmony_ci    let paths = filenames
1966cdb10c1Sopenharmony_ci        .iter()
1976cdb10c1Sopenharmony_ci        .map(|f| directory.join(f).to_str().unwrap().to_owned());
1986cdb10c1Sopenharmony_ci
1996cdb10c1Sopenharmony_ci    // Prevent wildcards from matching path separators to ensure that the search
2006cdb10c1Sopenharmony_ci    // is limited to the specified directory.
2016cdb10c1Sopenharmony_ci    let mut options = MatchOptions::new();
2026cdb10c1Sopenharmony_ci    options.require_literal_separator = true;
2036cdb10c1Sopenharmony_ci
2046cdb10c1Sopenharmony_ci    paths
2056cdb10c1Sopenharmony_ci        .map(|p| glob::glob_with(&p, options))
2066cdb10c1Sopenharmony_ci        .filter_map(Result::ok)
2076cdb10c1Sopenharmony_ci        .flatten()
2086cdb10c1Sopenharmony_ci        .filter_map(|p| {
2096cdb10c1Sopenharmony_ci            let path = p.ok()?;
2106cdb10c1Sopenharmony_ci            let filename = path.file_name()?.to_str().unwrap();
2116cdb10c1Sopenharmony_ci
2126cdb10c1Sopenharmony_ci            // The `libclang_shared` library has been renamed to `libclang-cpp`
2136cdb10c1Sopenharmony_ci            // in Clang 10. This can cause instances of this library (e.g.,
2146cdb10c1Sopenharmony_ci            // `libclang-cpp.so.10`) to be matched by patterns looking for
2156cdb10c1Sopenharmony_ci            // instances of `libclang`.
2166cdb10c1Sopenharmony_ci            if filename.contains("-cpp.") {
2176cdb10c1Sopenharmony_ci                return None;
2186cdb10c1Sopenharmony_ci            }
2196cdb10c1Sopenharmony_ci
2206cdb10c1Sopenharmony_ci            Some((directory.to_owned(), filename.into()))
2216cdb10c1Sopenharmony_ci        })
2226cdb10c1Sopenharmony_ci        .collect::<Vec<_>>()
2236cdb10c1Sopenharmony_ci}
2246cdb10c1Sopenharmony_ci
2256cdb10c1Sopenharmony_ci/// Finds the files in a directory (and any relevant sibling directories) that
2266cdb10c1Sopenharmony_ci/// match one or more filename glob patterns and returns the paths to and
2276cdb10c1Sopenharmony_ci/// filenames of those files.
2286cdb10c1Sopenharmony_cifn search_directories(directory: &Path, filenames: &[String]) -> Vec<(PathBuf, String)> {
2296cdb10c1Sopenharmony_ci    let mut results = search_directory(directory, filenames);
2306cdb10c1Sopenharmony_ci
2316cdb10c1Sopenharmony_ci    // On Windows, `libclang.dll` is usually found in the LLVM `bin` directory
2326cdb10c1Sopenharmony_ci    // while `libclang.lib` is usually found in the LLVM `lib` directory. To
2336cdb10c1Sopenharmony_ci    // keep things consistent with other platforms, only LLVM `lib` directories
2346cdb10c1Sopenharmony_ci    // are included in the backup search directory globs so we need to search
2356cdb10c1Sopenharmony_ci    // the LLVM `bin` directory here.
2366cdb10c1Sopenharmony_ci    if cfg!(target_os = "windows") && directory.ends_with("lib") {
2376cdb10c1Sopenharmony_ci        let sibling = directory.parent().unwrap().join("bin");
2386cdb10c1Sopenharmony_ci        results.extend(search_directory(&sibling, filenames).into_iter());
2396cdb10c1Sopenharmony_ci    }
2406cdb10c1Sopenharmony_ci
2416cdb10c1Sopenharmony_ci    results
2426cdb10c1Sopenharmony_ci}
2436cdb10c1Sopenharmony_ci
2446cdb10c1Sopenharmony_ci/// Finds the `libclang` static or dynamic libraries matching one or more
2456cdb10c1Sopenharmony_ci/// filename glob patterns and returns the paths to and filenames of those files.
2466cdb10c1Sopenharmony_cipub fn search_libclang_directories(filenames: &[String], variable: &str) -> Vec<(PathBuf, String)> {
2476cdb10c1Sopenharmony_ci    // Search only the path indicated by the relevant environment variable
2486cdb10c1Sopenharmony_ci    // (e.g., `LIBCLANG_PATH`) if it is set.
2496cdb10c1Sopenharmony_ci    if let Ok(path) = env::var(variable).map(|d| Path::new(&d).to_path_buf()) {
2506cdb10c1Sopenharmony_ci        // Check if the path is a matching file.
2516cdb10c1Sopenharmony_ci        if let Some(parent) = path.parent() {
2526cdb10c1Sopenharmony_ci            let filename = path.file_name().unwrap().to_str().unwrap();
2536cdb10c1Sopenharmony_ci            let libraries = search_directories(parent, filenames);
2546cdb10c1Sopenharmony_ci            if libraries.iter().any(|(_, f)| f == filename) {
2556cdb10c1Sopenharmony_ci                return vec![(parent.into(), filename.into())];
2566cdb10c1Sopenharmony_ci            }
2576cdb10c1Sopenharmony_ci        }
2586cdb10c1Sopenharmony_ci
2596cdb10c1Sopenharmony_ci        // Check if the path is directory containing a matching file.
2606cdb10c1Sopenharmony_ci        return search_directories(&path, filenames);
2616cdb10c1Sopenharmony_ci    }
2626cdb10c1Sopenharmony_ci
2636cdb10c1Sopenharmony_ci    let mut found = vec![];
2646cdb10c1Sopenharmony_ci
2656cdb10c1Sopenharmony_ci    // Search the `bin` and `lib` directories in the directory returned by
2666cdb10c1Sopenharmony_ci    // `llvm-config --prefix`.
2676cdb10c1Sopenharmony_ci    if let Some(output) = run_llvm_config(&["--prefix"]) {
2686cdb10c1Sopenharmony_ci        let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
2696cdb10c1Sopenharmony_ci        found.extend(search_directories(&directory.join("bin"), filenames));
2706cdb10c1Sopenharmony_ci        found.extend(search_directories(&directory.join("lib"), filenames));
2716cdb10c1Sopenharmony_ci        found.extend(search_directories(&directory.join("lib64"), filenames));
2726cdb10c1Sopenharmony_ci    }
2736cdb10c1Sopenharmony_ci
2746cdb10c1Sopenharmony_ci    // Search the toolchain directory in the directory returned by
2756cdb10c1Sopenharmony_ci    // `xcode-select --print-path`.
2766cdb10c1Sopenharmony_ci    if cfg!(target_os = "macos") {
2776cdb10c1Sopenharmony_ci        if let Some(output) = run_xcode_select(&["--print-path"]) {
2786cdb10c1Sopenharmony_ci            let directory = Path::new(output.lines().next().unwrap()).to_path_buf();
2796cdb10c1Sopenharmony_ci            let directory = directory.join("Toolchains/XcodeDefault.xctoolchain/usr/lib");
2806cdb10c1Sopenharmony_ci            found.extend(search_directories(&directory, filenames));
2816cdb10c1Sopenharmony_ci        }
2826cdb10c1Sopenharmony_ci    }
2836cdb10c1Sopenharmony_ci
2846cdb10c1Sopenharmony_ci    // Search the directories in the `LD_LIBRARY_PATH` environment variable.
2856cdb10c1Sopenharmony_ci    if let Ok(path) = env::var("LD_LIBRARY_PATH") {
2866cdb10c1Sopenharmony_ci        for directory in env::split_paths(&path) {
2876cdb10c1Sopenharmony_ci            found.extend(search_directories(&directory, filenames));
2886cdb10c1Sopenharmony_ci        }
2896cdb10c1Sopenharmony_ci    }
2906cdb10c1Sopenharmony_ci
2916cdb10c1Sopenharmony_ci    // Determine the `libclang` directory patterns.
2926cdb10c1Sopenharmony_ci    let directories = if cfg!(target_os = "haiku") {
2936cdb10c1Sopenharmony_ci        DIRECTORIES_HAIKU
2946cdb10c1Sopenharmony_ci    } else if cfg!(any(target_os = "linux", target_os = "freebsd")) {
2956cdb10c1Sopenharmony_ci        DIRECTORIES_LINUX
2966cdb10c1Sopenharmony_ci    } else if cfg!(target_os = "macos") {
2976cdb10c1Sopenharmony_ci        DIRECTORIES_MACOS
2986cdb10c1Sopenharmony_ci    } else if cfg!(target_os = "windows") {
2996cdb10c1Sopenharmony_ci        DIRECTORIES_WINDOWS
3006cdb10c1Sopenharmony_ci    } else if cfg!(target_os = "illumos") {
3016cdb10c1Sopenharmony_ci        DIRECTORIES_ILLUMOS
3026cdb10c1Sopenharmony_ci    } else {
3036cdb10c1Sopenharmony_ci        &[]
3046cdb10c1Sopenharmony_ci    };
3056cdb10c1Sopenharmony_ci
3066cdb10c1Sopenharmony_ci    // Search the directories provided by the `libclang` directory patterns.
3076cdb10c1Sopenharmony_ci    let mut options = MatchOptions::new();
3086cdb10c1Sopenharmony_ci    options.case_sensitive = false;
3096cdb10c1Sopenharmony_ci    options.require_literal_separator = true;
3106cdb10c1Sopenharmony_ci    for directory in directories.iter().rev() {
3116cdb10c1Sopenharmony_ci        if let Ok(directories) = glob::glob_with(directory, options) {
3126cdb10c1Sopenharmony_ci            for directory in directories.filter_map(Result::ok).filter(|p| p.is_dir()) {
3136cdb10c1Sopenharmony_ci                found.extend(search_directories(&directory, filenames));
3146cdb10c1Sopenharmony_ci            }
3156cdb10c1Sopenharmony_ci        }
3166cdb10c1Sopenharmony_ci    }
3176cdb10c1Sopenharmony_ci
3186cdb10c1Sopenharmony_ci    found
3196cdb10c1Sopenharmony_ci}
320