1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
use crate::{color::*, error::*, file::*, marshalling::*, metadata::*, reconcile::*};
use camino::Utf8Path;
use std::{collections::HashMap, fmt, fs::remove_file, time::SystemTime};
/// `bpt make-repo` [Bpt] file reconciler. Handles creating, removing, and updating binary [Bpt]
/// files to align with the available set of [Bbuild] files.
pub struct BptReconciler<'a> {
current: HashMap<PkgId, CurrentBpt<'a>>,
target: HashMap<PkgId, TargetBpt<'a>>,
}
pub struct CurrentBpt<'a> {
/// A currently-on-disk [Bpt]'s modified time.
///
/// If the underlying [Bbuild]s has been updated since the current [Bpt] has been
/// created, the current [Bpt] may have outdated information and needs to be rebuilt.
mtime: SystemTime,
/// File path to the [Bpt]. This is used to delete it if it is not in the target set.
path: &'a Utf8Path,
}
pub struct TargetBpt<'a> {
/// The target [Bpt] modified time.
///
/// This is the modified time the underlying [Bbuild]. The corresponding [Bpt]
/// should be at least as new; if it isn't, it needs to be rebuilt.
mtime: SystemTime,
/// The [Bbuild] defining how to build the target [Bpt]
bbuild: &'a Bbuild,
}
impl<'a> BptReconciler<'a> {
pub fn new(
// `bpt make-repo` collects the system time, path, and type for every file in the
// directory. This is the most natural way for it to pass the information along to this
// reconciler.
bbuilds: &'a [(SystemTime, &Utf8Path, Bbuild)],
bpts: &'a [(SystemTime, &Utf8Path, Bpt)],
conf: &BptConf,
) -> Self {
let current = bpts
.iter()
.map(|(mtime, path, bpt)| {
(
bpt.pkgid().clone(),
CurrentBpt {
mtime: *mtime,
path,
},
)
})
.collect::<HashMap<_, _>>();
let mut target = HashMap::new();
for arch in &conf.make_repo.archs {
for (mtime, _, bbuild) in bbuilds.iter() {
if bbuild.pkginfo().makearchs.as_slice().contains(arch) {
target.insert(
bbuild.pkgid().with_arch(*arch),
TargetBpt {
mtime: *mtime,
bbuild,
},
);
}
}
}
Self { current, target }
}
}
impl<'a> Reconciler<'a> for BptReconciler<'a> {
type Key = PkgId;
type Current = CurrentBpt<'a>;
type Target = TargetBpt<'a>;
type ApplyArgs = BuildArgs<'a>;
fn cmp(_key: &Self::Key, current: &Self::Current, target: &Self::Target) -> std::cmp::Ordering {
// We need to upgrade if the existing bpt is older (timestamp is less) than the bbuild, as
// that indicates the bbuild has been updated since the bpt was created.
use std::cmp::Ordering::*;
match current.mtime.cmp(&target.mtime) {
Less => Less,
Equal | Greater => Equal,
}
}
fn current(&self) -> &HashMap<Self::Key, Self::Current> {
&self.current
}
fn target(&self) -> &HashMap<Self::Key, Self::Target> {
&self.target
}
fn create(key: &Self::Key, target: &Self::Target, args: &Self::ApplyArgs) -> Result<(), Err> {
let bpt = target.bbuild.build(args, key.arch)?;
let path = args.out_dir.join(key.canonical_filename());
bpt.link(&path)
}
fn remove(
_key: &Self::Key,
current: &Self::Current,
_args: &Self::ApplyArgs,
) -> Result<(), Err> {
remove_file(current.path).map_err(|e| Err::Remove(current.path.to_string(), e))
}
fn upgrade(
key: &Self::Key,
current: &Self::Current,
target: &Self::Target,
args: &Self::ApplyArgs,
) -> Result<(), Err> {
let bpt = target.bbuild.build(args, key.arch)?;
// We may be rebuilding an out-of-date package, e.g. if its bbuild was updated and has a
// newer timestamp.
//
// In such a case, we should first clear the old package that is blocking our output path
// before linking the built package.
if let Err(e) = remove_file(current.path) {
if e.kind() != std::io::ErrorKind::NotFound {
return Err(Err::Remove(current.path.to_string(), e));
}
}
bpt.link(current.path)
}
fn apply_plan(plan: &ReconcilePlan<'a, Self>, args: &Self::ApplyArgs) -> Result<(), Err> {
let mut create = HashMap::new();
for (key, target) in &plan.create {
create.insert((*key).clone(), *target);
}
let mut upgrade = HashMap::new();
for (key, current, target) in &plan.upgrade {
upgrade.insert((*key).clone(), (*current, *target));
}
let sorted_targets = sort_build_targets(
create
.iter()
.map(|(pkgid, target)| BuildTarget {
pkgid: pkgid.clone(),
bbuild: target.bbuild,
arch: pkgid.arch,
})
.chain(upgrade.iter().map(|(pkgid, (_, target))| BuildTarget {
pkgid: pkgid.clone(),
bbuild: target.bbuild,
arch: pkgid.arch,
}))
.collect(),
)?;
// Build everything first. This can fail due to dependency cycles, missing deps, or build
// errors. Avoid mutating on-disk repo files until this phase succeeds.
let mut built = Vec::new();
for target in sorted_targets {
let bpt = target.bbuild.build(args, target.arch)?;
args.available_bpts.borrow_mut().add(bpt);
built.push(target.pkgid);
}
// With builds complete, apply output mutations.
for pkgid in built {
let out_path = if let Some((current, _)) = upgrade.get(&pkgid) {
if let Err(e) = remove_file(current.path) {
if e.kind() != std::io::ErrorKind::NotFound {
return Err(Err::Remove(current.path.to_string(), e));
}
}
current.path.to_owned()
} else {
args.out_dir.join(pkgid.canonical_filename())
};
args.available_bpts
.borrow()
.get(&pkgid)
.ok_or_else(|| Err::UnableToLocateRepositoryPkg(pkgid.to_pkgidpart()))?
.link(&out_path)?;
}
// Remove stale entries last, since failure here leaves extra files around but doesn't
// jeopardize newly built/updated outputs.
for (key, current) in &plan.remove {
Self::remove(key, current, args)?;
args.available_bpts.borrow_mut().remove(key);
}
Ok(())
}
fn create_desc(
key: &Self::Key,
_target: &Self::Target,
f: &mut fmt::Formatter<'_>,
) -> std::fmt::Result {
writeln!(
f,
"{}Create{} {}",
Color::Create,
Color::Default,
key.color().canonical_filename()
)
}
fn remove_desc(
key: &Self::Key,
_current: &Self::Current,
f: &mut fmt::Formatter<'_>,
) -> std::fmt::Result {
writeln!(
f,
"{}Remove{} {}",
Color::Remove,
Color::Default,
key.color().canonical_filename()
)
}
fn upgrade_desc(
key: &Self::Key,
_current: &Self::Current,
_target: &Self::Target,
f: &mut fmt::Formatter<'_>,
) -> std::fmt::Result {
writeln!(
f,
"{}Update{} {}",
Color::Upgrade,
Color::Default,
key.color().canonical_filename()
)
}
}