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
240
241
242
243
244
245
246
247
248
249
250
251
252
use crate::{color::*, error::*, file::*, io::*, marshalling::*, metadata::*, reconcile::*};
use camino::Utf8Path;
use std::{
collections::{HashMap, HashSet},
fmt,
fs::File,
fs::remove_file,
str::FromStr,
time::SystemTime,
};
/// `bpt make-repo` [FileIdx] file reconciler. Handles creating, removing, and updating [FileIdx]
/// files to align with the available set of [Bbuild] and [Bpt] files.
pub struct FileIdxReconciler<'a> {
current: HashMap<Arch, CurrentFileIdx<'a>>,
target: HashMap<Arch, TargetFileIdx>,
}
pub struct CurrentFileIdx<'a> {
/// A currently-on-disk [FileIdx]'s modified time.
///
/// If one of the underlying [Bbuild]s has been updated since the current [FileIdx] has been
/// updated, the current [FileIdx] may have outdated information and needs to be rebuilt.
mtime: SystemTime,
/// File path to the [FileIdx]. This is used to delete it if it is not in the target set.
path: &'a Utf8Path,
/// The list of packages within the currently-on-disk [FileIdx].
///
/// If this does not one-to-one match the list target list of packages - packages have
/// been added, removed, or updated - we need to rebuild the [FileIdx].
pkgids: HashSet<PkgId>,
}
pub struct TargetFileIdx {
/// The target [FileIdx] modified time.
///
/// This is the newest modified time of any underlying [Bbuild]. The corresponding [FileIdx]
/// should be at least as new; if it isn't, it needs to be rebuilt.
mtime: SystemTime,
/// The target list of packages within the [FileIdx].
///
/// If this does not one-to-one match the list target list of packages - packages have
/// been added, removed, or updated - we need to rebuild the [FileIdx].
pkgids: HashSet<PkgId>,
}
pub struct FileIdxRecArgs<'a> {
/// Directory containing input/output files.
pub dir: &'a Utf8Path,
/// Key to sign created files
pub privkey: &'a PrivKey,
/// `bpt make-repo` creates [Reconciler]s and displays their [ReconcilePlan]s for
/// confirmation before it executes any of the [ReconcilePlan]s. This means the [Bpt]s needed
/// to build [FileIdx]s may not yet be available when [FileIdxReconciler::new] is run.
///
/// We need public keys to load the [Bpt]s during eventual execution of the plan.
pub pubkeys: &'a PublicKeys,
}
impl<'a> FileIdxReconciler<'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)],
fileidxs: &'a [(SystemTime, &Utf8Path, FileIdx)],
conf: &BptConf,
) -> Result<Self, Err> {
let mut current = HashMap::new();
for (mtime, path, fileidx) in fileidxs {
let arch: Arch = path
.file_stem()
.map(Arch::from_str)
.ok_or_else(|| Err::FilenameStemArch(path.to_string()))?
.map_err(|_| Err::FilenameStemArch(path.to_string()))?;
let pkgids = fileidx.pkgids().cloned().collect::<HashSet<PkgId>>();
current.insert(
arch,
CurrentFileIdx {
mtime: *mtime,
path,
pkgids,
},
);
}
let mut target = HashMap::new();
for arch in conf.make_repo.archs.iter().filter(|&&a| a != Arch::bbuild) {
let mut target_pkgids = HashSet::new();
let mut target_mtime = SystemTime::UNIX_EPOCH;
for (mtime, _, bbuild) in bbuilds.iter() {
if !bbuild.pkginfo().makearchs.as_slice().contains(arch) {
continue;
}
target_pkgids.insert(bbuild.pkgid().with_arch(*arch));
if *mtime > target_mtime {
target_mtime = *mtime;
}
}
target.insert(
*arch,
TargetFileIdx {
mtime: target_mtime,
pkgids: target_pkgids,
},
);
}
Ok(Self { current, target })
}
}
impl<'a> Reconciler<'a> for FileIdxReconciler<'a> {
type Key = Arch;
type Current = CurrentFileIdx<'a>;
type Target = TargetFileIdx;
type ApplyArgs = FileIdxRecArgs<'a>;
fn cmp(
_arch: &Self::Key,
current: &Self::Current,
target: &Self::Target,
) -> std::cmp::Ordering {
// We need to update the fileidx if:
// - Any target package is newer than the index, as this could indicate new paths
// - The target set of packages does not match the contained set of packages.
if current.mtime < target.mtime || current.pkgids != target.pkgids {
std::cmp::Ordering::Less
} else {
std::cmp::Ordering::Equal
}
}
fn current(&self) -> &HashMap<Self::Key, Self::Current> {
&self.current
}
fn target(&self) -> &HashMap<Self::Key, Self::Target> {
&self.target
}
fn create(arch: &Self::Key, target: &Self::Target, args: &Self::ApplyArgs) -> Result<(), Err> {
let bpts = collect_bpts(&target.pkgids, args.dir, args.pubkeys)?;
let fileidx = FileIdx::from_bpts(&bpts, args.dir, args.privkey)?;
fileidx.link(&args.dir.join(format!("{arch}.fileidx")))
}
fn remove(
_arch: &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(
_arch: &Self::Key,
current: &Self::Current,
target: &Self::Target,
args: &Self::ApplyArgs,
) -> Result<(), Err> {
let bpts = collect_bpts(&target.pkgids, args.dir, args.pubkeys)?;
let fileidx = FileIdx::from_bpts(&bpts, args.dir, args.privkey)?;
// We may be rebuilding an out-of-date index, e.g. if source files updated.
//
// In such a case, we should first clear the old index 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));
}
}
fileidx.link(current.path)
}
fn create_desc(
arch: &Self::Key,
_target: &Self::Target,
f: &mut fmt::Formatter<'_>,
) -> std::fmt::Result {
writeln!(
f,
"{}Create {}{}{}.{}fileidx{}",
Color::Create,
Color::Arch,
arch,
Color::Glue,
Color::File,
Color::Default,
)
}
fn remove_desc(
arch: &Self::Key,
_current: &Self::Current,
f: &mut fmt::Formatter<'_>,
) -> std::fmt::Result {
writeln!(
f,
"{}Remove {}{}{}.{}fileidx{}",
Color::Remove,
Color::Arch,
arch,
Color::Glue,
Color::File,
Color::Default,
)
}
fn upgrade_desc(
arch: &Self::Key,
_current: &Self::Current,
_target: &Self::Target,
f: &mut fmt::Formatter<'_>,
) -> std::fmt::Result {
writeln!(
f,
"{}Update {}{}{}.{}fileidx{}",
Color::Upgrade,
Color::Arch,
arch,
Color::Glue,
Color::File,
Color::Default,
)
}
}
fn collect_bpts(
pkgids: &HashSet<PkgId>,
dir: &Utf8Path,
pubkeys: &PublicKeys,
) -> Result<Vec<Bpt>, Err> {
let mut bpts = Vec::new();
for path in dir.readdir()? {
match path.extension() {
Some("bpt") => {
let file = File::open_ro(&path)?;
let bpt = Bpt::from_file(file, pubkeys).loc(path)?;
if pkgids.contains(bpt.pkgid()) {
bpts.push(bpt);
}
}
_ => continue,
}
}
bpts.sort_by_key(|a| a.pkgid().to_string());
Ok(bpts)
}