Skip to main content

upstream_rs/services/builder/profiles/
dotnet.rs

1use std::path::{Path, PathBuf};
2use std::process::Command;
3
4use anyhow::{Context, Result, anyhow, bail};
5
6use crate::services::builder::{
7    BuildProfile,
8    profiles::{BuildProfileHandler, emit_line_callback, run_command_with_line_callback},
9};
10
11pub struct DotnetProfile;
12
13impl DotnetProfile {
14    fn binary_name(package_name: &str) -> String {
15        #[cfg(windows)]
16        {
17            format!("{package_name}.exe")
18        }
19        #[cfg(not(windows))]
20        {
21            package_name.to_string()
22        }
23    }
24
25    fn find_project_dir(workspace: &Path) -> Option<PathBuf> {
26        let has_root = std::fs::read_dir(workspace).ok().is_some_and(|entries| {
27            entries.flatten().any(|e| {
28                e.path()
29                    .extension()
30                    .is_some_and(|ext| ext == "sln" || ext == "csproj")
31            })
32        });
33        if has_root {
34            Some(workspace.to_path_buf())
35        } else {
36            None
37        }
38    }
39}
40
41impl BuildProfileHandler for DotnetProfile {
42    fn profile(&self) -> BuildProfile {
43        BuildProfile::Dotnet
44    }
45
46    fn detect(&self, workspace: &Path) -> bool {
47        Self::find_project_dir(workspace).is_some()
48    }
49
50    fn run_build(
51        &self,
52        workspace: &Path,
53        package_name: &str,
54        line_callback: &mut Option<&mut dyn FnMut(&str)>,
55    ) -> Result<PathBuf> {
56        let project_dir = Self::find_project_dir(workspace).ok_or_else(|| {
57            anyhow!(
58                "Could not find a .sln or .csproj in repository root '{}'.",
59                workspace.display()
60            )
61        })?;
62
63        let publish_dir = project_dir.join(".upstream-build").join("publish");
64        std::fs::create_dir_all(&publish_dir).context(format!(
65            "Failed to create dotnet publish directory '{}'",
66            publish_dir.display()
67        ))?;
68
69        emit_line_callback(line_callback, "Running dotnet publish ...");
70        let status = run_command_with_line_callback(
71            Command::new("dotnet")
72                .arg("publish")
73                .arg("-c")
74                .arg("Release")
75                .arg("-o")
76                .arg(&publish_dir)
77                .current_dir(&project_dir),
78            "Failed to run 'dotnet publish'. Is .NET SDK installed?",
79            line_callback,
80        )?;
81
82        if !status.success() {
83            bail!("Dotnet publish failed for '{}'", package_name);
84        }
85
86        let candidate = publish_dir.join(Self::binary_name(package_name));
87
88        if !candidate.exists() {
89            return Err(anyhow!(
90                "Dotnet publish succeeded but artifact was not found at '{}'",
91                candidate.display()
92            ));
93        }
94
95        Ok(candidate)
96    }
97}