prebuilts/
lib.rs

1// Copyright 2022 The ChromiumOS Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::env;
6use std::path::Path;
7use std::path::PathBuf;
8
9use anyhow::anyhow;
10use anyhow::Result;
11use named_lock::NamedLock;
12
13mod sys;
14
15static BASE_URL: &str = "https://storage.googleapis.com/chromeos-localmirror/distfiles/prebuilts/";
16static DOWNLOAD_RETRIES: usize = 3;
17
18// Returns `deps` directory for the current build.
19fn get_deps_directory() -> Result<PathBuf> {
20    let out_dir = env::var("OUT_DIR")
21        .ok()
22        .ok_or_else(|| anyhow!("OUT_DIR is not set"))?;
23
24    let dest = PathBuf::from(&out_dir)
25        .parent()
26        .ok_or_else(|| anyhow!("../ not found for {:?}", out_dir))?
27        .parent()
28        .ok_or_else(|| anyhow!("../../ not found for {:?}", out_dir))?
29        .parent()
30        .ok_or_else(|| anyhow!("../../../ not found for {:?}", out_dir))?
31        .join("deps");
32    if dest.is_dir() {
33        Ok(dest)
34    } else {
35        Err(anyhow!(
36            "deps({:?}) directory not found OUT_DIR: {:?}",
37            dest,
38            out_dir
39        ))
40    }
41}
42
43// We download the prebuilt into deps directory and create a symlink to the downloaded prebuilt in
44// deps parent directory.
45// The symlink will help windows find the dll when an executable is manually run.
46// For example, `file` is downloaded in
47// `target/x86_64-pc-windows-gnu/release/deps/` and a `link` will be crated in
48// `target/x86_64-pc-windows-gnu/release/`.
49// Any executable in those two directories will be able to find the dlls they depend as in the same
50// directory.
51struct PrebuiltPath {
52    file: PathBuf,
53    link: PathBuf,
54}
55
56fn get_dest_path(filename: &str) -> Result<PrebuiltPath> {
57    let deps = get_deps_directory()?;
58
59    Ok(PrebuiltPath {
60        file: deps.join(filename),
61        link: deps.parent().unwrap().join(filename),
62    })
63}
64
65fn get_url(library: &str, filename: &str, version: u32) -> String {
66    let build_type = if env::var("DEBUG").is_ok() {
67        "debug"
68    } else {
69        "release"
70    };
71    let platform = env::var("CARGO_CFG_TARGET_FAMILY").unwrap();
72    let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
73    let toolchain = env::var("CARGO_CFG_TARGET_ENV").unwrap();
74
75    format!("{BASE_URL}{platform}/{arch}/{toolchain}/{library}/{build_type}/{version}/{filename}",)
76}
77
78pub fn download_file(url: &str, destination: &Path) -> Result<()> {
79    let lock = NamedLock::create("crosvm_prebuilts_download")?;
80    let _guard = lock.lock()?;
81
82    // Another process may have already downloaded this since we last checked.
83    if destination.exists() {
84        println!("Prebuilt {destination:?} has already been downloaded by another process.");
85        return Ok(());
86    }
87
88    println!("Downloading prebuilt {url} to {destination:?}");
89    let mut attempts_left = DOWNLOAD_RETRIES + 1;
90    loop {
91        attempts_left -= 1;
92        let mut cmd = sys::download_command(url, destination);
93        match cmd.status() {
94            Ok(exit_code) => {
95                if !exit_code.success() {
96                    if attempts_left == 0 {
97                        return Err(anyhow!("Cannot download {}", url));
98                    } else {
99                        println!("Failed to download {url}. Retrying.");
100                    }
101                } else {
102                    return Ok(());
103                }
104            }
105            Err(error) => {
106                if attempts_left == 0 {
107                    return Err(anyhow!(error));
108                } else {
109                    println!("Failed to download {url}: {error:?}");
110                }
111            }
112        }
113    }
114}
115
116/// Downloads a prebuilt file, with name `filename` of `version` from the `library` into target's
117/// `deps` directory.
118pub fn download_prebuilt(library: &str, version: u32, filename: &str) -> Result<PathBuf> {
119    let dest_path = get_dest_path(filename)?;
120    let url = get_url(library, filename, version);
121
122    println!("downloading prebuilt:{} to:{:?}", url, dest_path.file);
123    download_file(&url, Path::new(&dest_path.file))?;
124    println!(
125        "creating symlink:{:?} linking to:{:?}",
126        dest_path.link, dest_path.file
127    );
128    let _ = std::fs::remove_file(&dest_path.link);
129    #[cfg(any(target_os = "android", target_os = "linux"))]
130    std::os::unix::fs::symlink(&dest_path.file, &dest_path.link)?;
131    #[cfg(windows)]
132    let _ = std::fs::copy(&dest_path.file, &dest_path.link)?;
133    Ok(dest_path.file)
134}
135
136/// Downloads a list of prebuilt file, with names in `filenames` of `version` from the `library`
137/// into target's `deps` directory.
138pub fn download_prebuilts(library: &str, version: u32, filenames: &[&str]) -> Result<Vec<PathBuf>> {
139    let mut paths = vec![];
140    for filename in filenames {
141        paths.push(download_prebuilt(library, version, filename)?);
142    }
143    Ok(paths)
144}