about summary refs log tree commit diff
path: root/compiler/rustc_codegen_gcc/build_system/src/config.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_codegen_gcc/build_system/src/config.rs')
-rw-r--r--compiler/rustc_codegen_gcc/build_system/src/config.rs647
1 files changed, 529 insertions, 118 deletions
diff --git a/compiler/rustc_codegen_gcc/build_system/src/config.rs b/compiler/rustc_codegen_gcc/build_system/src/config.rs
index 64d9bd73e01..c633ee57d4a 100644
--- a/compiler/rustc_codegen_gcc/build_system/src/config.rs
+++ b/compiler/rustc_codegen_gcc/build_system/src/config.rs
@@ -1,149 +1,560 @@
-use crate::utils::{get_gcc_path, get_os_name, get_rustc_host_triple};
+use crate::utils::{
+    create_symlink, get_os_name, run_command_with_output, rustc_version_info, split_args,
+};
 use std::collections::HashMap;
 use std::env as std_env;
+use std::ffi::OsStr;
+use std::fs;
+use std::path::{Path, PathBuf};
 
+use boml::{types::TomlValue, Toml};
+
+#[derive(Default, PartialEq, Eq, Clone, Copy, Debug)]
+pub enum Channel {
+    #[default]
+    Debug,
+    Release,
+}
+
+impl Channel {
+    pub fn as_str(self) -> &'static str {
+        match self {
+            Self::Debug => "debug",
+            Self::Release => "release",
+        }
+    }
+}
+
+fn failed_config_parsing(config_file: &Path, err: &str) -> Result<ConfigFile, String> {
+    Err(format!(
+        "Failed to parse `{}`: {}",
+        config_file.display(),
+        err
+    ))
+}
+
+#[derive(Default)]
+pub struct ConfigFile {
+    gcc_path: Option<String>,
+    download_gccjit: Option<bool>,
+}
+
+impl ConfigFile {
+    pub fn new(config_file: &Path) -> Result<Self, String> {
+        let content = fs::read_to_string(config_file).map_err(|_| {
+            format!(
+                "Failed to read `{}`. Take a look at `Readme.md` to see how to set up the project",
+                config_file.display(),
+            )
+        })?;
+        let toml = Toml::parse(&content).map_err(|err| {
+            format!(
+                "Error occurred around `{}`: {:?}",
+                &content[err.start..=err.end],
+                err.kind
+            )
+        })?;
+        let mut config = Self::default();
+        for (key, value) in toml.iter() {
+            match (key, value) {
+                ("gcc-path", TomlValue::String(value)) => {
+                    config.gcc_path = Some(value.as_str().to_string())
+                }
+                ("gcc-path", _) => {
+                    return failed_config_parsing(config_file, "Expected a string for `gcc-path`")
+                }
+                ("download-gccjit", TomlValue::Boolean(value)) => {
+                    config.download_gccjit = Some(*value)
+                }
+                ("download-gccjit", _) => {
+                    return failed_config_parsing(
+                        config_file,
+                        "Expected a boolean for `download-gccjit`",
+                    )
+                }
+                _ => return failed_config_parsing(config_file, &format!("Unknown key `{}`", key)),
+            }
+        }
+        match (config.gcc_path.as_mut(), config.download_gccjit) {
+            (None, None | Some(false)) => {
+                return failed_config_parsing(
+                    config_file,
+                    "At least one of `gcc-path` or `download-gccjit` value must be set",
+                )
+            }
+            (Some(_), Some(true)) => {
+                println!(
+                    "WARNING: both `gcc-path` and `download-gccjit` arguments are used, \
+                    ignoring `gcc-path`"
+                );
+            }
+            (Some(gcc_path), _) => {
+                let path = Path::new(gcc_path);
+                *gcc_path = path
+                    .canonicalize()
+                    .map_err(|err| {
+                        format!("Failed to get absolute path of `{}`: {:?}", gcc_path, err)
+                    })?
+                    .display()
+                    .to_string();
+            }
+            _ => {}
+        }
+        Ok(config)
+    }
+}
+
+#[derive(Default, Debug)]
 pub struct ConfigInfo {
     pub target: String,
     pub target_triple: String,
+    pub host_triple: String,
     pub rustc_command: Vec<String>,
+    pub run_in_vm: bool,
+    pub cargo_target_dir: String,
+    pub dylib_ext: String,
+    pub sysroot_release_channel: bool,
+    pub channel: Channel,
+    pub sysroot_panic_abort: bool,
+    pub cg_backend_path: String,
+    pub sysroot_path: String,
+    pub gcc_path: String,
+    config_file: Option<String>,
+    // This is used in particular in rust compiler bootstrap because it doesn't run at the root
+    // of the `cg_gcc` folder, making it complicated for us to get access to local files we need
+    // like `libgccjit.version` or `config.toml`.
+    cg_gcc_path: Option<PathBuf>,
+    // Needed for the `info` command which doesn't want to actually download the lib if needed,
+    // just to set the `gcc_path` field to display it.
+    pub no_download: bool,
+    pub no_default_features: bool,
 }
 
-// Returns the beginning for the command line of rustc.
-pub fn set_config(
-    env: &mut HashMap<String, String>,
-    test_flags: &[String],
-    gcc_path: Option<&str>,
-) -> Result<ConfigInfo, String> {
-    env.insert("CARGO_INCREMENTAL".to_string(), "0".to_string());
-
-    let gcc_path = match gcc_path {
-        Some(path) => path.to_string(),
-        None => get_gcc_path()?,
-    };
-    env.insert("GCC_PATH".to_string(), gcc_path.clone());
-
-    let os_name = get_os_name()?;
-    let dylib_ext = match os_name.as_str() {
-        "Linux" => "so",
-        "Darwin" => "dylib",
-        os => return Err(format!("unsupported OS `{}`", os)),
-    };
-    let host_triple = get_rustc_host_triple()?;
-    let mut linker = None;
-    let mut target_triple = host_triple.clone();
-    let mut target = target_triple.clone();
-
-    // We skip binary name and the command.
-    let mut args = std::env::args().skip(2);
-
-    let mut set_target_triple = false;
-    let mut set_target = false;
-    while let Some(arg) = args.next() {
-        match arg.as_str() {
-            "--target-triple" => {
+impl ConfigInfo {
+    /// Returns `true` if the argument was taken into account.
+    pub fn parse_argument(
+        &mut self,
+        arg: &str,
+        args: &mut impl Iterator<Item = String>,
+    ) -> Result<bool, String> {
+        match arg {
+            "--target" => {
                 if let Some(arg) = args.next() {
-                    target_triple = arg;
-                    set_target_triple = true;
+                    self.target = arg;
                 } else {
+                    return Err("Expected a value after `--target`, found nothing".to_string());
+                }
+            }
+            "--target-triple" => match args.next() {
+                Some(arg) if !arg.is_empty() => self.target_triple = arg.to_string(),
+                _ => {
                     return Err(
                         "Expected a value after `--target-triple`, found nothing".to_string()
-                    );
+                    )
                 }
             },
-            "--target" => {
-                if let Some(arg) = args.next() {
-                    target = arg;
-                    set_target = true;
-                } else {
-                    return Err(
-                        "Expected a value after `--target`, found nothing".to_string()
-                    );
+            "--out-dir" => match args.next() {
+                Some(arg) if !arg.is_empty() => {
+                    self.cargo_target_dir = arg.to_string();
+                }
+                _ => return Err("Expected a value after `--out-dir`, found nothing".to_string()),
+            },
+            "--config-file" => match args.next() {
+                Some(arg) if !arg.is_empty() => {
+                    self.config_file = Some(arg.to_string());
+                }
+                _ => {
+                    return Err("Expected a value after `--config-file`, found nothing".to_string())
                 }
             },
-            _ => (),
+            "--release-sysroot" => self.sysroot_release_channel = true,
+            "--release" => self.channel = Channel::Release,
+            "--sysroot-panic-abort" => self.sysroot_panic_abort = true,
+            "--cg_gcc-path" => match args.next() {
+                Some(arg) if !arg.is_empty() => {
+                    self.cg_gcc_path = Some(arg.into());
+                }
+                _ => {
+                    return Err("Expected a value after `--cg_gcc-path`, found nothing".to_string())
+                }
+            },
+            "--no-default-features" => self.no_default_features = true,
+            _ => return Ok(false),
         }
+        Ok(true)
     }
 
-    if set_target_triple && !set_target {
-        target = target_triple.clone();
+    pub fn rustc_command_vec(&self) -> Vec<&dyn AsRef<OsStr>> {
+        let mut command: Vec<&dyn AsRef<OsStr>> = Vec::with_capacity(self.rustc_command.len());
+        for arg in self.rustc_command.iter() {
+            command.push(arg);
+        }
+        command
     }
 
-    if host_triple != target_triple {
-        linker = Some(format!("-Clinker={}-gcc", target_triple));
+    pub fn get_gcc_commit(&self) -> Result<String, String> {
+        let commit_hash_file = self.compute_path("libgccjit.version");
+        let content = fs::read_to_string(&commit_hash_file).map_err(|_| {
+            format!(
+                "Failed to read `{}`. Take a look at `Readme.md` to see how to set up the project",
+                commit_hash_file.display(),
+            )
+        })?;
+        let commit = content.trim();
+        // This is a very simple check to ensure this is not a path. For the rest, it'll just fail
+        // when trying to download the file so we should be fine.
+        if commit.contains('/') || commit.contains('\\') {
+            return Err(format!(
+                "{}: invalid commit hash `{}`",
+                commit_hash_file.display(),
+                commit,
+            ));
+        }
+        Ok(commit.to_string())
     }
-    let current_dir =
-        std_env::current_dir().map_err(|error| format!("`current_dir` failed: {:?}", error))?;
-    let channel = if let Some(channel) = env.get("CHANNEL") {
-        channel.as_str()
-    } else {
-        "debug"
-    };
-    let cg_backend_path = current_dir
-        .join("target")
-        .join(channel)
-        .join(&format!("librustc_codegen_gcc.{}", dylib_ext));
-    let sysroot_path = current_dir.join("build_sysroot/sysroot");
-    let mut rustflags = Vec::new();
-    if let Some(cg_rustflags) = env.get("CG_RUSTFLAGS") {
-        rustflags.push(cg_rustflags.clone());
+
+    fn download_gccjit_if_needed(&mut self) -> Result<(), String> {
+        let output_dir = Path::new(crate::BUILD_DIR).join("libgccjit");
+        let commit = self.get_gcc_commit()?;
+
+        let output_dir = output_dir.join(&commit);
+        if !output_dir.is_dir() {
+            std::fs::create_dir_all(&output_dir).map_err(|err| {
+                format!(
+                    "failed to create folder `{}`: {:?}",
+                    output_dir.display(),
+                    err,
+                )
+            })?;
+        }
+        let output_dir = output_dir.canonicalize().map_err(|err| {
+            format!(
+                "Failed to get absolute path of `{}`: {:?}",
+                output_dir.display(),
+                err
+            )
+        })?;
+
+        let libgccjit_so_name = "libgccjit.so";
+        let libgccjit_so = output_dir.join(libgccjit_so_name);
+        if !libgccjit_so.is_file() && !self.no_download {
+            // Download time!
+            let tempfile_name = format!("{}.download", libgccjit_so_name);
+            let tempfile = output_dir.join(&tempfile_name);
+            let is_in_ci = std::env::var("GITHUB_ACTIONS").is_ok();
+
+            let url = format!(
+                "https://github.com/antoyo/gcc/releases/download/master-{}/libgccjit.so",
+                commit,
+            );
+
+            println!("Downloading `{}`...", url);
+            download_gccjit(url, &output_dir, tempfile_name, !is_in_ci)?;
+
+            let libgccjit_so = output_dir.join(libgccjit_so_name);
+            // If we reach this point, it means the file was correctly downloaded, so let's
+            // rename it!
+            std::fs::rename(&tempfile, &libgccjit_so).map_err(|err| {
+                format!(
+                    "Failed to rename `{}` into `{}`: {:?}",
+                    tempfile.display(),
+                    libgccjit_so.display(),
+                    err,
+                )
+            })?;
+
+            println!("Downloaded libgccjit.so version {} successfully!", commit);
+            // We need to create a link named `libgccjit.so.0` because that's what the linker is
+            // looking for.
+            create_symlink(
+                &libgccjit_so,
+                output_dir.join(&format!("{}.0", libgccjit_so_name)),
+            )?;
+        }
+
+        self.gcc_path = output_dir.display().to_string();
+        println!("Using `{}` as path for libgccjit", self.gcc_path);
+        Ok(())
     }
-    if let Some(linker) = linker {
-        rustflags.push(linker.to_string());
+
+    pub fn compute_path<P: AsRef<Path>>(&self, other: P) -> PathBuf {
+        match self.cg_gcc_path {
+            Some(ref path) => path.join(other),
+            None => PathBuf::new().join(other),
+        }
     }
-    rustflags.extend_from_slice(&[
-        "-Csymbol-mangling-version=v0".to_string(),
-        "-Cdebuginfo=2".to_string(),
-        format!("-Zcodegen-backend={}", cg_backend_path.display()),
-        "--sysroot".to_string(),
-        sysroot_path.display().to_string(),
-    ]);
-
-    // Since we don't support ThinLTO, disable LTO completely when not trying to do LTO.
-    // TODO(antoyo): remove when we can handle ThinLTO.
-    if !env.contains_key(&"FAT_LTO".to_string()) {
-        rustflags.push("-Clto=off".to_string());
+
+    pub fn setup_gcc_path(&mut self) -> Result<(), String> {
+        let config_file = match self.config_file.as_deref() {
+            Some(config_file) => config_file.into(),
+            None => self.compute_path("config.toml"),
+        };
+        let ConfigFile {
+            gcc_path,
+            download_gccjit,
+        } = ConfigFile::new(&config_file)?;
+
+        if let Some(true) = download_gccjit {
+            self.download_gccjit_if_needed()?;
+            return Ok(());
+        }
+        self.gcc_path = match gcc_path {
+            Some(path) => path,
+            None => {
+                return Err(format!(
+                    "missing `gcc-path` value from `{}`",
+                    config_file.display(),
+                ))
+            }
+        };
+        Ok(())
     }
-    rustflags.extend_from_slice(test_flags);
-    // FIXME(antoyo): remove once the atomic shim is gone
-    if os_name == "Darwin" {
+
+    pub fn setup(
+        &mut self,
+        env: &mut HashMap<String, String>,
+        use_system_gcc: bool,
+    ) -> Result<(), String> {
+        env.insert("CARGO_INCREMENTAL".to_string(), "0".to_string());
+
+        if self.gcc_path.is_empty() && !use_system_gcc {
+            self.setup_gcc_path()?;
+        }
+        env.insert("GCC_PATH".to_string(), self.gcc_path.clone());
+
+        if self.cargo_target_dir.is_empty() {
+            match env.get("CARGO_TARGET_DIR").filter(|dir| !dir.is_empty()) {
+                Some(cargo_target_dir) => self.cargo_target_dir = cargo_target_dir.clone(),
+                None => self.cargo_target_dir = "target/out".to_string(),
+            }
+        }
+
+        let os_name = get_os_name()?;
+        self.dylib_ext = match os_name.as_str() {
+            "Linux" => "so",
+            "Darwin" => "dylib",
+            os => return Err(format!("unsupported OS `{}`", os)),
+        }
+        .to_string();
+        let rustc = match env.get("RUSTC") {
+            Some(r) if !r.is_empty() => r.to_string(),
+            _ => "rustc".to_string(),
+        };
+        self.host_triple = match rustc_version_info(Some(&rustc))?.host {
+            Some(host) => host,
+            None => return Err("no host found".to_string()),
+        };
+
+        if self.target_triple.is_empty() {
+            if let Some(overwrite) = env.get("OVERWRITE_TARGET_TRIPLE") {
+                self.target_triple = overwrite.clone();
+            }
+        }
+        if self.target_triple.is_empty() {
+            self.target_triple = self.host_triple.clone();
+        }
+        if self.target.is_empty() && !self.target_triple.is_empty() {
+            self.target = self.target_triple.clone();
+        }
+
+        let mut linker = None;
+
+        if self.host_triple != self.target_triple {
+            if self.target_triple.is_empty() {
+                return Err("Unknown non-native platform".to_string());
+            }
+            linker = Some(format!("-Clinker={}-gcc", self.target_triple));
+            self.run_in_vm = true;
+        }
+
+        let current_dir =
+            std_env::current_dir().map_err(|error| format!("`current_dir` failed: {:?}", error))?;
+        let channel = if self.channel == Channel::Release {
+            "release"
+        } else if let Some(channel) = env.get("CHANNEL") {
+            channel.as_str()
+        } else {
+            "debug"
+        };
+
+        let has_builtin_backend = env
+            .get("BUILTIN_BACKEND")
+            .map(|backend| !backend.is_empty())
+            .unwrap_or(false);
+
+        let mut rustflags = Vec::new();
+        if has_builtin_backend {
+            // It means we're building inside the rustc testsuite, so some options need to be handled
+            // a bit differently.
+            self.cg_backend_path = "gcc".to_string();
+
+            match env.get("RUSTC_SYSROOT") {
+                Some(rustc_sysroot) if !rustc_sysroot.is_empty() => {
+                    rustflags.extend_from_slice(&["--sysroot".to_string(), rustc_sysroot.clone()]);
+                }
+                _ => {}
+            }
+            // This should not be needed, but is necessary for the CI in the rust repository.
+            // FIXME: Remove when the rust CI switches to the master version of libgccjit.
+            rustflags.push("-Cpanic=abort".to_string());
+        } else {
+            self.cg_backend_path = current_dir
+                .join("target")
+                .join(channel)
+                .join(&format!("librustc_codegen_gcc.{}", self.dylib_ext))
+                .display()
+                .to_string();
+            self.sysroot_path = current_dir
+                .join("build_sysroot/sysroot")
+                .display()
+                .to_string();
+            rustflags.extend_from_slice(&["--sysroot".to_string(), self.sysroot_path.clone()]);
+        };
+
+        // This environment variable is useful in case we want to change options of rustc commands.
+        if let Some(cg_rustflags) = env.get("CG_RUSTFLAGS") {
+            rustflags.extend_from_slice(&split_args(&cg_rustflags)?);
+        }
+        if let Some(test_flags) = env.get("TEST_FLAGS") {
+            rustflags.extend_from_slice(&split_args(&test_flags)?);
+        }
+
+        if let Some(linker) = linker {
+            rustflags.push(linker.to_string());
+        }
+
+        if self.no_default_features {
+            rustflags.push("-Csymbol-mangling-version=v0".to_string());
+        }
+
         rustflags.extend_from_slice(&[
-            "-Clink-arg=-undefined".to_string(),
-            "-Clink-arg=dynamic_lookup".to_string(),
+            "-Cdebuginfo=2".to_string(),
+            format!("-Zcodegen-backend={}", self.cg_backend_path),
+        ]);
+
+        // Since we don't support ThinLTO, disable LTO completely when not trying to do LTO.
+        // TODO(antoyo): remove when we can handle ThinLTO.
+        if !env.contains_key(&"FAT_LTO".to_string()) {
+            rustflags.push("-Clto=off".to_string());
+        }
+        // FIXME(antoyo): remove once the atomic shim is gone
+        if os_name == "Darwin" {
+            rustflags.extend_from_slice(&[
+                "-Clink-arg=-undefined".to_string(),
+                "-Clink-arg=dynamic_lookup".to_string(),
+            ]);
+        }
+        env.insert("RUSTFLAGS".to_string(), rustflags.join(" "));
+        // display metadata load errors
+        env.insert("RUSTC_LOG".to_string(), "warn".to_string());
+
+        let sysroot = current_dir.join(&format!(
+            "build_sysroot/sysroot/lib/rustlib/{}/lib",
+            self.target_triple,
+        ));
+        let ld_library_path = format!(
+            "{target}:{sysroot}:{gcc_path}",
+            // FIXME: It's possible to pick another out directory. Would be nice to have a command
+            // line option to change it.
+            target = current_dir.join("target/out").display(),
+            sysroot = sysroot.display(),
+            gcc_path = self.gcc_path,
+        );
+        env.insert("LIBRARY_PATH".to_string(), ld_library_path.clone());
+        env.insert("LD_LIBRARY_PATH".to_string(), ld_library_path.clone());
+        env.insert("DYLD_LIBRARY_PATH".to_string(), ld_library_path);
+
+        // NOTE: To avoid the -fno-inline errors, use /opt/gcc/bin/gcc instead of cc.
+        // To do so, add a symlink for cc to /opt/gcc/bin/gcc in our PATH.
+        // Another option would be to add the following Rust flag: -Clinker=/opt/gcc/bin/gcc
+        let path = std::env::var("PATH").unwrap_or_default();
+        env.insert(
+            "PATH".to_string(),
+            format!(
+                "/opt/gcc/bin:/opt/m68k-unknown-linux-gnu/bin{}{}",
+                if path.is_empty() { "" } else { ":" },
+                path
+            ),
+        );
+
+        self.rustc_command = vec![rustc];
+        self.rustc_command.extend_from_slice(&rustflags);
+        self.rustc_command.extend_from_slice(&[
+            "-L".to_string(),
+            "crate=target/out".to_string(),
+            "--out-dir".to_string(),
+            self.cargo_target_dir.clone(),
         ]);
+
+        if !env.contains_key("RUSTC_LOG") {
+            env.insert("RUSTC_LOG".to_string(), "warn".to_string());
+        }
+        Ok(())
     }
-    env.insert("RUSTFLAGS".to_string(), rustflags.join(" "));
-    // display metadata load errors
-    env.insert("RUSTC_LOG".to_string(), "warn".to_string());
-
-    let sysroot = current_dir.join(&format!(
-        "build_sysroot/sysroot/lib/rustlib/{}/lib",
-        target_triple
-    ));
-    let ld_library_path = format!(
-        "{target}:{sysroot}:{gcc_path}",
-        target = current_dir.join("target/out").display(),
-        sysroot = sysroot.display(),
+
+    pub fn show_usage() {
+        println!(
+            "\
+    --target-triple [arg]  : Set the target triple to [arg]
+    --target [arg]         : Set the target to [arg]
+    --out-dir              : Location where the files will be generated
+    --release              : Build in release mode
+    --release-sysroot      : Build sysroot in release mode
+    --sysroot-panic-abort  : Build the sysroot without unwinding support
+    --config-file          : Location of the config file to be used
+    --cg_gcc-path          : Location of the rustc_codegen_gcc root folder (used
+                             when ran from another directory)
+    --no-default-features  : Add `--no-default-features` flag to cargo commands"
+        );
+    }
+}
+
+fn download_gccjit(
+    url: String,
+    output_dir: &Path,
+    tempfile_name: String,
+    with_progress_bar: bool,
+) -> Result<(), String> {
+    // Try curl. If that fails and we are on windows, fallback to PowerShell.
+    let mut ret = run_command_with_output(
+        &[
+            &"curl",
+            &"--speed-time",
+            &"30",
+            &"--speed-limit",
+            &"10", // timeout if speed is < 10 bytes/sec for > 30 seconds
+            &"--connect-timeout",
+            &"30", // timeout if cannot connect within 30 seconds
+            &"-o",
+            &tempfile_name,
+            &"--retry",
+            &"3",
+            &"-SRfL",
+            if with_progress_bar {
+                &"--progress-bar"
+            } else {
+                &"-s"
+            },
+            &url.as_str(),
+        ],
+        Some(&output_dir),
     );
-    env.insert("LD_LIBRARY_PATH".to_string(), ld_library_path.clone());
-    env.insert("DYLD_LIBRARY_PATH".to_string(), ld_library_path);
-
-    // NOTE: To avoid the -fno-inline errors, use /opt/gcc/bin/gcc instead of cc.
-    // To do so, add a symlink for cc to /opt/gcc/bin/gcc in our PATH.
-    // Another option would be to add the following Rust flag: -Clinker=/opt/gcc/bin/gcc
-    let path = std::env::var("PATH").unwrap_or_default();
-    env.insert("PATH".to_string(), format!("/opt/gcc/bin:{}", path));
-
-    let mut rustc_command = vec!["rustc".to_string()];
-    rustc_command.extend_from_slice(&rustflags);
-    rustc_command.extend_from_slice(&[
-        "-L".to_string(),
-        "crate=target/out".to_string(),
-        "--out-dir".to_string(),
-        "target/out".to_string(),
-    ]);
-    Ok(ConfigInfo {
-        target,
-        target_triple,
-        rustc_command,
-    })
+    if ret.is_err() && cfg!(windows) {
+        eprintln!("Fallback to PowerShell");
+        ret = run_command_with_output(
+            &[
+                &"PowerShell.exe",
+                &"/nologo",
+                &"-Command",
+                &"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;",
+                &format!(
+                    "(New-Object System.Net.WebClient).DownloadFile('{}', '{}')",
+                    url, tempfile_name,
+                )
+                .as_str(),
+            ],
+            Some(&output_dir),
+        );
+    }
+    ret
 }