Skip to main content

nifty_codegen/
link.rs

1//! Links a compiled app's object file with the Nifty runtime library into an executable,
2//! with the system's own linker.
3
4use std::path::Path;
5use std::process::Command;
6
7use crate::CodegenError;
8
9/// The file name of the runtime library every compiled app links against, as `nifty-rt`
10/// builds it; `nf` is installed next to it.
11#[cfg(target_os = "macos")]
12pub const RUNTIME_LIBRARY: &str = "libnifty_rt.a";
13#[cfg(windows)]
14pub const RUNTIME_LIBRARY: &str = "nifty_rt.lib";
15
16/// Links `object` with `runtime`, the runtime library, into the executable `executable`,
17/// replacing it.
18///
19/// # Errors
20/// When the linker can't be found or run, or it fails.
21pub fn link(object: &Path, runtime: &Path, executable: &Path) -> Result<(), CodegenError> {
22    #[cfg(target_os = "macos")]
23    let mut linker = linker(object, runtime, executable);
24    #[cfg(windows)]
25    let mut linker = linker(object, runtime, executable)?;
26    let output = linker.output().map_err(|error| {
27        CodegenError::new(format!("can't run the linker: {error}{LINKER_HINT}"))
28    })?;
29    if output.status.success() {
30        Ok(())
31    } else {
32        Err(CodegenError::new(format!(
33            "linking {} failed:\n{}",
34            executable.display(),
35            String::from_utf8_lossy(&output.stderr).trim_end()
36        )))
37    }
38}
39
40/// What to do when the linker can't be run at all.
41#[cfg(target_os = "macos")]
42const LINKER_HINT: &str = "; install Xcode's command line tools with `xcode-select --install`";
43#[cfg(windows)]
44const LINKER_HINT: &str = "";
45
46/// Xcode's command line tools provide `cc`, which drives the system linker.
47#[cfg(target_os = "macos")]
48fn linker(object: &Path, runtime: &Path, executable: &Path) -> Command {
49    let mut command = Command::new("cc");
50    command
51        // Leaves out the parts of the runtime library the app never reaches, and the names
52        // of its private functions, which only a debugger reads.
53        .args(["-Wl,-dead_strip", "-Wl,-x"])
54        .arg("-o")
55        .arg(executable)
56        .arg(object)
57        .arg(runtime)
58        .args(nifty_rt::NATIVE_LIBS);
59    command
60}
61
62/// Installs the Build Tools for Visual Studio with the C++ workload, which has the linker.
63#[cfg(windows)]
64const INSTALL_BUILD_TOOLS: &str = "winget install Microsoft.VisualStudio.2022.BuildTools \
65     --override \"--passive --wait --add Microsoft.VisualStudio.Workload.VCTools \
66     --includeRecommended\"";
67
68/// The MSVC linker, found where Visual Studio or its Build Tools installed it.
69#[cfg(windows)]
70fn linker(object: &Path, runtime: &Path, executable: &Path) -> Result<Command, CodegenError> {
71    /// Selects Common Controls 6 and per-monitor DPI awareness, as for `nf` itself.
72    const MANIFEST: &str = include_str!("../../nifty-cli/windows/nifty.manifest");
73    #[cfg(target_arch = "x86_64")]
74    const TARGET: &str = "x86_64-pc-windows-msvc";
75    #[cfg(target_arch = "aarch64")]
76    const TARGET: &str = "aarch64-pc-windows-msvc";
77
78    let tool = cc::windows_registry::find_tool(TARGET, "link.exe").ok_or_else(|| {
79        CodegenError::new(format!(
80            "building apps needs Microsoft's C++ Build Tools, which link them; install them \
81             with `{INSTALL_BUILD_TOOLS}`"
82        ))
83    })?;
84    let manifest = object.with_file_name("nifty.manifest");
85    std::fs::write(&manifest, MANIFEST).map_err(|error| {
86        CodegenError::new(format!("can't write {}: {error}", manifest.display()))
87    })?;
88    let mut command = tool.to_command();
89    command
90        .arg("/NOLOGO")
91        .arg(format!("/OUT:{}", executable.display()))
92        .arg(object)
93        .arg(runtime)
94        .args(nifty_rt::NATIVE_LIBS)
95        .args([
96            "/SUBSYSTEM:WINDOWS",
97            "/ENTRY:mainCRTStartup",
98            "/MANIFEST:EMBED",
99        ])
100        .arg(format!("/MANIFESTINPUT:{}", manifest.display()));
101    Ok(command)
102}