Skip to main content

objdiff_core/jobs/
objdiff.rs

1use std::{sync::mpsc::Receiver, task::Waker};
2
3use anyhow::{Error, Result, bail};
4use time::OffsetDateTime;
5use typed_path::Utf8PlatformPathBuf;
6
7use crate::{
8    build::{BuildConfig, BuildStatus, run_make},
9    diff::{DiffObjConfig, DiffSide, MappingConfig, ObjectDiff, diff_objs},
10    jobs::{Job, JobContext, JobResult, JobState, start_job, update_status},
11    obj::{Object, read},
12};
13
14pub struct ObjDiffConfig {
15    pub build_config: BuildConfig,
16    pub build_base: bool,
17    pub build_target: bool,
18    pub target_path: Option<Utf8PlatformPathBuf>,
19    pub base_path: Option<Utf8PlatformPathBuf>,
20    pub diff_obj_config: DiffObjConfig,
21    pub mapping_config: MappingConfig,
22}
23
24pub struct ObjDiffResult {
25    pub first_status: BuildStatus,
26    pub second_status: BuildStatus,
27    pub first_obj: Option<(Object, ObjectDiff)>,
28    pub second_obj: Option<(Object, ObjectDiff)>,
29    pub time: OffsetDateTime,
30}
31
32fn run_build(
33    context: &JobContext,
34    cancel: Receiver<()>,
35    config: ObjDiffConfig,
36) -> Result<Box<ObjDiffResult>> {
37    let mut target_path_rel = None;
38    let mut base_path_rel = None;
39    if config.build_target || config.build_base {
40        let project_dir = config
41            .build_config
42            .project_dir
43            .as_ref()
44            .ok_or_else(|| Error::msg("Missing project dir"))?;
45        if let Some(target_path) = &config.target_path {
46            target_path_rel = match target_path.strip_prefix(project_dir) {
47                Ok(p) => Some(p.with_unix_encoding()),
48                Err(_) => {
49                    bail!("Target path '{}' doesn't begin with '{}'", target_path, project_dir);
50                }
51            };
52        }
53        if let Some(base_path) = &config.base_path {
54            base_path_rel = match base_path.strip_prefix(project_dir) {
55                Ok(p) => Some(p.with_unix_encoding()),
56                Err(_) => {
57                    bail!("Base path '{}' doesn't begin with '{}'", base_path, project_dir);
58                }
59            };
60        };
61    }
62
63    let mut total = 1;
64    if config.build_target && target_path_rel.is_some() {
65        total += 1;
66    }
67    if config.build_base && base_path_rel.is_some() {
68        total += 1;
69    }
70    if config.target_path.is_some() {
71        total += 1;
72    }
73    if config.base_path.is_some() {
74        total += 1;
75    }
76
77    let mut step_idx = 0;
78    let mut first_status = match target_path_rel {
79        Some(target_path_rel) if config.build_target => {
80            update_status(
81                context,
82                format!("Building target {target_path_rel}"),
83                step_idx,
84                total,
85                &cancel,
86            )?;
87            step_idx += 1;
88            run_make(&config.build_config, target_path_rel.as_ref())
89        }
90        _ => BuildStatus::default(),
91    };
92
93    let mut second_status = match base_path_rel {
94        Some(base_path_rel) if config.build_base => {
95            update_status(
96                context,
97                format!("Building base {base_path_rel}"),
98                step_idx,
99                total,
100                &cancel,
101            )?;
102            step_idx += 1;
103            run_make(&config.build_config, base_path_rel.as_ref())
104        }
105        _ => BuildStatus::default(),
106    };
107
108    let time = OffsetDateTime::now_utc();
109
110    let first_obj = match &config.target_path {
111        Some(target_path) if first_status.success => {
112            update_status(
113                context,
114                format!("Loading target {target_path}"),
115                step_idx,
116                total,
117                &cancel,
118            )?;
119            step_idx += 1;
120            match read::read(target_path.as_ref(), &config.diff_obj_config, DiffSide::Target) {
121                Ok(obj) => Some(obj),
122                Err(e) => {
123                    first_status = BuildStatus {
124                        success: false,
125                        stdout: format!("Loading object '{target_path}'"),
126                        stderr: format!("{e:#}"),
127                        ..Default::default()
128                    };
129                    None
130                }
131            }
132        }
133        Some(_) => {
134            step_idx += 1;
135            None
136        }
137        _ => None,
138    };
139
140    let second_obj = match &config.base_path {
141        Some(base_path) if second_status.success => {
142            update_status(context, format!("Loading base {base_path}"), step_idx, total, &cancel)?;
143            step_idx += 1;
144            match read::read(base_path.as_ref(), &config.diff_obj_config, DiffSide::Base) {
145                Ok(obj) => Some(obj),
146                Err(e) => {
147                    second_status = BuildStatus {
148                        success: false,
149                        stdout: format!("Loading object '{base_path}'"),
150                        stderr: format!("{e:#}"),
151                        ..Default::default()
152                    };
153                    None
154                }
155            }
156        }
157        Some(_) => {
158            step_idx += 1;
159            None
160        }
161        _ => None,
162    };
163
164    update_status(context, "Performing diff".to_string(), step_idx, total, &cancel)?;
165    step_idx += 1;
166    let result = diff_objs(
167        first_obj.as_ref(),
168        second_obj.as_ref(),
169        None,
170        &config.diff_obj_config,
171        &config.mapping_config,
172    )?;
173
174    update_status(context, "Complete".to_string(), step_idx, total, &cancel)?;
175    Ok(Box::new(ObjDiffResult {
176        first_status,
177        second_status,
178        first_obj: first_obj.zip(result.left),
179        second_obj: second_obj.zip(result.right),
180        time,
181    }))
182}
183
184pub fn start_build(waker: Waker, config: ObjDiffConfig) -> JobState {
185    start_job(waker, "Build", Job::ObjDiff, move |context, cancel| {
186        run_build(&context, cancel, config).map(|result| JobResult::ObjDiff(Some(result)))
187    })
188}