Compare commits
2
Commits
9e0b6a37a6
...
43f8a9e275
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43f8a9e275
|
||
|
|
ff41edbd47 |
@@ -92,6 +92,8 @@ pkh pull hello # needs -d ubuntu if you are not running Ubuntu
|
|||||||
git add debian/patches/xxx.patch
|
git add debian/patches/xxx.patch
|
||||||
git commit -m "Applied patch xxx"
|
git commit -m "Applied patch xxx"
|
||||||
pkh chlog
|
pkh chlog
|
||||||
|
git add debian/changelog
|
||||||
|
git commit -m "d/changelog"
|
||||||
# Test that the package builds
|
# Test that the package builds
|
||||||
pkh build
|
pkh build
|
||||||
pkh deb
|
pkh deb
|
||||||
@@ -101,54 +103,11 @@ pkh put --ppa user/hello_xxx
|
|||||||
git push xxx user-fork
|
git push xxx user-fork
|
||||||
```
|
```
|
||||||
|
|
||||||
## Roadmap: features needed for 1.0
|
## Future improvement ideas
|
||||||
|
|
||||||
Basically, wrapping the basic debian workflows.
|
|
||||||
Missing features:
|
|
||||||
- [ ] `pkh pull`
|
|
||||||
- [x] Obtain package sources from git
|
|
||||||
- [x] Obtain package sources from the archive (fallback)
|
|
||||||
- [x] Obtain package source from PPA (--ppa)
|
|
||||||
- [x] Obtain a specific version of the package
|
|
||||||
- [x] Fetch the correct git branch for series on Ubuntu
|
|
||||||
- [ ] Try to fetch the correct git branch for series on Debian, or fallback to the archive
|
|
||||||
- [ ] `pkh chlog`
|
|
||||||
- [x] Auto-generate changelog entry
|
|
||||||
- [x] Extra flags: backport, non-maintainer upload, no change rebuild, ...
|
|
||||||
- [x] Select the target series, matching changelog suite names with their series (unstable ≡ sid)
|
|
||||||
- [ ] Commit changelog entry
|
|
||||||
- [x] `pkh new`
|
|
||||||
- [x] Scaffold a new Debian source package (interactive, multiple languages)
|
|
||||||
- [ ] `pkh build`
|
|
||||||
- [x] Build the source package
|
|
||||||
- [ ] `pkh deb`
|
|
||||||
- [x] Build the binary package
|
|
||||||
- [x] Build for a specific architecture
|
|
||||||
- [ ] Three build modes:
|
|
||||||
- [x] Build locally (discouraged)
|
|
||||||
- [x] Build using unshare chroot, with binary emulation (default)
|
|
||||||
- [x] Cross-compilation
|
|
||||||
- [ ] Async build
|
|
||||||
- [ ] `pkh status`
|
|
||||||
- [ ] Show build status
|
|
||||||
- [ ] `pkh put`
|
|
||||||
- [x] Upload the source package to a PPA (native SFTP, no `dput` dependency)
|
|
||||||
- [x] Degrade to the anonymous FTP queue when the SSH connection never comes up (dput's upload method)
|
|
||||||
- [ ] Upload the source package to the archive
|
|
||||||
- [ ] `pkh commit`
|
|
||||||
- [ ] Commit the changes to git
|
|
||||||
- [x] `pkh lint`
|
|
||||||
- [x] Lint the package
|
|
||||||
- [x] `pkh prune`
|
|
||||||
- [x] Prune residual pkh build artifacts and caches
|
|
||||||
- [ ] `pkh test`
|
|
||||||
- [ ] Run autopkgtest
|
|
||||||
- [ ] Provide options: local (discouraged), chroot, VM?, ppa
|
|
||||||
- [ ] Async test
|
|
||||||
|
|
||||||
## Nice-to-have features
|
|
||||||
|
|
||||||
- [ ] 'pkh pull'
|
|
||||||
- [ ] Cache the Sources.gz files, to improve speed
|
|
||||||
- [ ] Work in an already downloaded package, to git pull and re-fetch orig tar gz
|
|
||||||
|
|
||||||
|
- pull: try to fetch the correct git branch for series on Debian
|
||||||
|
- deb: asynchronous build, detachable and monitorable
|
||||||
|
- put: allow uploads to Debian or Ubuntu archives
|
||||||
|
- test: add 'pkh test' to run autopkgtests
|
||||||
|
- pull: cache Sources.gz files to improve speed
|
||||||
|
- pull: 'pkh pull' in a package tree should git pull and re-fetch orig tgz
|
||||||
|
|||||||
+87
-23
@@ -50,8 +50,11 @@ struct Pipeline {
|
|||||||
|
|
||||||
/// State shared between [`DebUi`] and its sinks
|
/// State shared between [`DebUi`] and its sinks
|
||||||
struct Shared {
|
struct Shared {
|
||||||
|
multi: MultiProgress,
|
||||||
top: ProgressBar,
|
top: ProgressBar,
|
||||||
pane: ProgressBar,
|
/// The rolling pane bar, created on demand: flows without subprocess
|
||||||
|
/// output (e.g. `pkh put`) never show it at all
|
||||||
|
pane: Mutex<Option<ProgressBar>>,
|
||||||
state: Mutex<Pipeline>,
|
state: Mutex<Pipeline>,
|
||||||
tee: Mutex<Option<File>>,
|
tee: Mutex<Option<File>>,
|
||||||
log_path: Mutex<PathBuf>,
|
log_path: Mutex<PathBuf>,
|
||||||
@@ -85,34 +88,18 @@ impl DebUi {
|
|||||||
pb.enable_steady_tick(Duration::from_millis(80));
|
pb.enable_steady_tick(Duration::from_millis(80));
|
||||||
pb.set_style(spinner_style());
|
pb.set_style(spinner_style());
|
||||||
pb.set_prefix("Building package");
|
pb.set_prefix("Building package");
|
||||||
pb.set_message("(starting…)");
|
|
||||||
pb
|
|
||||||
} else {
|
|
||||||
ProgressBar::hidden()
|
|
||||||
};
|
|
||||||
|
|
||||||
let pane = if enabled {
|
|
||||||
let pb = multi.add(ProgressBar::new(0));
|
|
||||||
pb.enable_steady_tick(Duration::from_millis(150));
|
|
||||||
// No template margin: multi-line messages are only prefixed by
|
|
||||||
// the template on their first line, which would misalign the
|
|
||||||
// pane; each rendered line carries its own indent instead.
|
|
||||||
pb.set_style(
|
|
||||||
ProgressStyle::default_bar()
|
|
||||||
.template("{msg}")
|
|
||||||
.expect("valid template"),
|
|
||||||
);
|
|
||||||
pb.set_message(" │ (starting…)");
|
|
||||||
pb
|
pb
|
||||||
} else {
|
} else {
|
||||||
ProgressBar::hidden()
|
ProgressBar::hidden()
|
||||||
};
|
};
|
||||||
|
let pane = Mutex::new(None);
|
||||||
|
|
||||||
let timestamp = chrono::Utc::now().format("%Y%m%dT%H%M%S").to_string();
|
let timestamp = chrono::Utc::now().format("%Y%m%dT%H%M%S").to_string();
|
||||||
let log_path = default_log_path(×tamp);
|
let log_path = default_log_path(×tamp);
|
||||||
|
|
||||||
let ui = Self {
|
let ui = Self {
|
||||||
shared: Arc::new(Shared {
|
shared: Arc::new(Shared {
|
||||||
|
multi: multi.clone(),
|
||||||
top,
|
top,
|
||||||
pane,
|
pane,
|
||||||
state: Mutex::new(Pipeline {
|
state: Mutex::new(Pipeline {
|
||||||
@@ -198,7 +185,7 @@ impl DebUi {
|
|||||||
if self.shared.enabled {
|
if self.shared.enabled {
|
||||||
self.shared.top.set_style(spinner_style());
|
self.shared.top.set_style(spinner_style());
|
||||||
self.shared.top.set_message(label.to_string());
|
self.shared.top.set_message(label.to_string());
|
||||||
self.shared.pane.set_message("");
|
drop_pane(&self.shared);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,9 +208,8 @@ impl DebUi {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.shared.top.disable_steady_tick();
|
self.shared.top.disable_steady_tick();
|
||||||
self.shared.pane.disable_steady_tick();
|
drop_pane(&self.shared);
|
||||||
self.shared.top.finish_and_clear();
|
self.shared.top.finish_and_clear();
|
||||||
self.shared.pane.finish_and_clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Success outcome body: clear the widget and print the artifacts,
|
/// Success outcome body: clear the widget and print the artifacts,
|
||||||
@@ -408,7 +394,44 @@ fn push_line(shared: &Shared, st: &mut Pipeline, kind: Kind, text: String) {
|
|||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
if now.duration_since(st.last_draw) >= REDRAW_INTERVAL {
|
if now.duration_since(st.last_draw) >= REDRAW_INTERVAL {
|
||||||
st.last_draw = now;
|
st.last_draw = now;
|
||||||
shared.pane.set_message(render_pane(&st.lines));
|
if let Some(pane) = ensure_pane(shared) {
|
||||||
|
pane.set_message(render_pane(&st.lines));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The pane bar, added to the terminal on the first call and reused after
|
||||||
|
///
|
||||||
|
/// Returns `None` once the widget is suspended: a line racing the suspend
|
||||||
|
/// must not re-add a bar the cleanup just cleared.
|
||||||
|
fn ensure_pane(shared: &Shared) -> Option<ProgressBar> {
|
||||||
|
let mut pane = shared.pane.lock().unwrap();
|
||||||
|
if let Some(pb) = pane.as_ref() {
|
||||||
|
return Some(pb.clone());
|
||||||
|
}
|
||||||
|
if shared.suspended.load(Ordering::SeqCst) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let pb = shared.multi.add(ProgressBar::new(0));
|
||||||
|
pb.enable_steady_tick(Duration::from_millis(150));
|
||||||
|
// No template margin: multi-line messages are only prefixed by the
|
||||||
|
// template on their first line, which would misalign the pane; each
|
||||||
|
// rendered line carries its own indent instead.
|
||||||
|
pb.set_style(
|
||||||
|
ProgressStyle::default_bar()
|
||||||
|
.template("{msg}")
|
||||||
|
.expect("valid template"),
|
||||||
|
);
|
||||||
|
*pane = Some(pb.clone());
|
||||||
|
Some(pb)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Take the pane bar off the terminal; the next pushed line re-creates it
|
||||||
|
fn drop_pane(shared: &Shared) {
|
||||||
|
if let Some(pb) = shared.pane.lock().unwrap().take() {
|
||||||
|
pb.disable_steady_tick();
|
||||||
|
pb.finish_and_clear();
|
||||||
|
shared.multi.remove(&pb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,6 +661,47 @@ mod tests {
|
|||||||
assert!(rendered.contains("a line that would overflow a narrow pane"));
|
assert!(rendered.contains("a line that would overflow a narrow pane"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A fresh view has no pane at all: flows without subprocess output
|
||||||
|
/// (`pkh put`) must not render anything until a line arrives, and a
|
||||||
|
/// dropped or suspended pane stays gone
|
||||||
|
#[test]
|
||||||
|
fn pane_is_created_lazily_and_dropped_cleanly() {
|
||||||
|
let multi = MultiProgress::new();
|
||||||
|
let shared = Shared {
|
||||||
|
multi: multi.clone(),
|
||||||
|
top: multi.add(ProgressBar::new(0)),
|
||||||
|
pane: Mutex::new(None),
|
||||||
|
state: Mutex::new(Pipeline {
|
||||||
|
classifier: Box::new(GenericClassifier::new()),
|
||||||
|
lines: VecDeque::new(),
|
||||||
|
errors: Vec::new(),
|
||||||
|
last_draw: Instant::now(),
|
||||||
|
bar_total: 0,
|
||||||
|
}),
|
||||||
|
tee: Mutex::new(None),
|
||||||
|
log_path: Mutex::new(std::env::temp_dir().join("pkh-pane-test.log")),
|
||||||
|
timestamp: String::new(),
|
||||||
|
enabled: true,
|
||||||
|
suspended: AtomicBool::new(false),
|
||||||
|
started: Instant::now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(shared.pane.lock().unwrap().is_none());
|
||||||
|
|
||||||
|
ensure_pane(&shared).unwrap();
|
||||||
|
assert!(shared.pane.lock().unwrap().is_some());
|
||||||
|
// Later lines hit the stored bar instead of stacking another one
|
||||||
|
ensure_pane(&shared).unwrap();
|
||||||
|
assert!(shared.pane.lock().unwrap().is_some());
|
||||||
|
|
||||||
|
drop_pane(&shared);
|
||||||
|
assert!(shared.pane.lock().unwrap().is_none());
|
||||||
|
|
||||||
|
// A line racing the suspend must not re-add the cleared bar
|
||||||
|
shared.suspended.store(true, Ordering::SeqCst);
|
||||||
|
assert!(ensure_pane(&shared).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
/// Best-effort ANSI escape stripper, enough for the assertions above
|
/// Best-effort ANSI escape stripper, enough for the assertions above
|
||||||
fn strip_ansi(line: &str) -> String {
|
fn strip_ansi(line: &str) -> String {
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user