freenet_test_network/
binary.rs

1use crate::{Error, Result};
2use std::path::{Path, PathBuf};
3
4/// Build profile for compiling freenet
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum BuildProfile {
7    Debug,
8    Release,
9}
10
11/// Specifies which freenet binary to use for the test network
12#[derive(Debug, Clone)]
13pub enum FreenetBinary {
14    /// Build and use binary from current cargo workspace
15    CurrentCrate(BuildProfile),
16
17    /// Use `freenet` binary from PATH
18    Installed,
19
20    /// Use binary at specific path
21    Path(PathBuf),
22
23    /// Build binary from a cargo workspace at specified path
24    /// Use this for worktrees or when freenet-core is in a different location
25    Workspace {
26        path: PathBuf,
27        profile: BuildProfile,
28    },
29}
30
31impl Default for FreenetBinary {
32    fn default() -> Self {
33        // Smart default: If in freenet-core workspace, use CurrentCrate with debug
34        // Otherwise, try Installed
35        if is_in_freenet_workspace() {
36            Self::CurrentCrate(BuildProfile::Debug)
37        } else if which::which("freenet").is_ok() {
38            Self::Installed
39        } else {
40            Self::Installed // Will error in resolve() with helpful message
41        }
42    }
43}
44
45impl FreenetBinary {
46    /// Resolve to actual binary path, building if necessary
47    pub fn resolve(&self) -> Result<PathBuf> {
48        match self {
49            Self::CurrentCrate(profile) => {
50                tracing::info!(
51                    "Building freenet binary from current workspace ({:?})",
52                    profile
53                );
54                build_current_workspace(*profile)
55            }
56            Self::Installed => which::which("freenet").map_err(|_| {
57                Error::InvalidBinary(
58                    "freenet binary not found in PATH. Install with: cargo install freenet".into(),
59                )
60            }),
61            Self::Path(p) => {
62                if !p.exists() {
63                    return Err(Error::InvalidBinary(format!(
64                        "Binary not found: {}",
65                        p.display()
66                    )));
67                }
68                Ok(p.clone())
69            }
70            Self::Workspace { path, profile } => {
71                tracing::info!(
72                    "Building freenet binary from workspace: {} ({:?})",
73                    path.display(),
74                    profile
75                );
76                build_workspace(path, *profile)
77            }
78        }
79    }
80}
81
82fn is_in_freenet_workspace() -> bool {
83    std::env::current_dir()
84        .ok()
85        .and_then(|dir| {
86            dir.ancestors()
87                .find(|p| p.join("Cargo.toml").exists())
88                .and_then(|p| std::fs::read_to_string(p.join("Cargo.toml")).ok())
89        })
90        .map(|content| content.contains("name = \"freenet\""))
91        .unwrap_or(false)
92}
93
94fn build_current_workspace(profile: BuildProfile) -> Result<PathBuf> {
95    // Get workspace root BEFORE building
96    // Find the Cargo workspace root (with [workspace] section), not just any Cargo.toml
97    let cwd = std::env::current_dir()?;
98    let workspace_root = cwd
99        .ancestors()
100        .find(|p| {
101            let cargo_toml = p.join("Cargo.toml");
102            if !cargo_toml.exists() {
103                return false;
104            }
105            // Check if this is a workspace root
106            std::fs::read_to_string(&cargo_toml)
107                .map(|content| content.contains("[workspace]"))
108                .unwrap_or(false)
109        })
110        .or_else(|| {
111            // Fallback: just find any Cargo.toml with freenet binary
112            cwd.ancestors().find(|p| {
113                let cargo_toml = p.join("Cargo.toml");
114                if !cargo_toml.exists() {
115                    return false;
116                }
117                std::fs::read_to_string(&cargo_toml)
118                    .map(|content| content.contains("name = \"freenet\""))
119                    .unwrap_or(false)
120            })
121        })
122        .ok_or_else(|| {
123            Error::InvalidBinary("Could not find workspace root with freenet binary".into())
124        })?
125        .to_path_buf();
126
127    let profile_arg = match profile {
128        BuildProfile::Debug => vec!["build"],
129        BuildProfile::Release => vec!["build", "--release"],
130    };
131
132    let mut cmd = std::process::Command::new("cargo");
133    cmd.args(&profile_arg)
134        .args(["--bin", "freenet"])
135        .current_dir(&workspace_root);
136
137    let output = cmd.output()?;
138
139    if !output.status.success() {
140        return Err(Error::InvalidBinary(format!(
141            "Failed to build freenet: {}",
142            String::from_utf8_lossy(&output.stderr)
143        )));
144    }
145
146    // Use workspace_root's target directory
147    let target_dir = if let Ok(target) = std::env::var("CARGO_TARGET_DIR") {
148        PathBuf::from(target)
149    } else {
150        workspace_root.join("target")
151    };
152
153    let profile_dir = match profile {
154        BuildProfile::Debug => "debug",
155        BuildProfile::Release => "release",
156    };
157    let binary = target_dir.join(format!("{}/freenet", profile_dir));
158
159    if !binary.exists() {
160        return Err(Error::InvalidBinary(format!(
161            "Built binary not found at: {} (workspace: {}, target_dir: {})",
162            binary.display(),
163            workspace_root.display(),
164            target_dir.display()
165        )));
166    }
167
168    Ok(binary)
169}
170
171fn build_workspace(workspace: &Path, profile: BuildProfile) -> Result<PathBuf> {
172    let profile_arg = match profile {
173        BuildProfile::Debug => vec!["build"],
174        BuildProfile::Release => vec!["build", "--release"],
175    };
176
177    let mut cmd = std::process::Command::new("cargo");
178    cmd.args(&profile_arg)
179        .args(["--bin", "freenet"])
180        .current_dir(workspace);
181
182    let output = cmd.output()?;
183
184    if !output.status.success() {
185        return Err(Error::InvalidBinary(format!(
186            "Failed to build freenet in {}: {}",
187            workspace.display(),
188            String::from_utf8_lossy(&output.stderr)
189        )));
190    }
191
192    let target_dir = workspace.join("target");
193    let profile_dir = match profile {
194        BuildProfile::Debug => "debug",
195        BuildProfile::Release => "release",
196    };
197    let binary = target_dir.join(format!("{}/freenet", profile_dir));
198
199    if !binary.exists() {
200        return Err(Error::InvalidBinary(format!(
201            "Built binary not found in target/{}",
202            profile_dir
203        )));
204    }
205
206    Ok(binary)
207}
208
209fn get_target_dir() -> Result<PathBuf> {
210    // Check CARGO_TARGET_DIR env var first
211    if let Ok(target) = std::env::var("CARGO_TARGET_DIR") {
212        return Ok(PathBuf::from(target));
213    }
214
215    // Find workspace root and use target/ there
216    std::env::current_dir()?
217        .ancestors()
218        .find(|p| p.join("Cargo.toml").exists())
219        .map(|p| p.join("target"))
220        .ok_or_else(|| Error::InvalidBinary("Could not find workspace root".into()))
221}