diff --git a/Cargo.lock b/Cargo.lock index d4e410e..ffdad9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,12 @@ dependencies = [ "libc", ] +[[package]] +name = "json" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -109,6 +115,7 @@ dependencies = [ "anyhow", "getopts", "jemallocator", + "json", "kernel32-sys", "lazy_static", "libc", diff --git a/Cargo.toml b/Cargo.toml index 5b6e4cf..9c88c19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ getopts = "0.2" anyhow = "1.0" libc = "0.2" lazy_static = "1.4.0" +json = "0.12.4" [target.'cfg(windows)'.dependencies] kernel32-sys = "0.2.2" diff --git a/src/json_progress.rs b/src/json_progress.rs new file mode 100644 index 0000000..aa01cee --- /dev/null +++ b/src/json_progress.rs @@ -0,0 +1,65 @@ +//! Writes progress updates as JSON messages to a stream. + +extern crate json; + +use std::io::Write; + +use crate::densemap::Index; +use crate::graph::{Build, BuildId}; +use crate::progress::Progress; +use crate::task::TaskResult; +use crate::work::{BuildState, StateCounts}; + +/// Implements progress::Progress by forwarding messages as JSON to a stream. +pub struct JSONProgress { + stream: Option>, +} + +impl JSONProgress { + pub fn new(path: &str) -> anyhow::Result { + let stream = Box::new(std::fs::OpenOptions::new().append(true).open(path)?); + Ok(JSONProgress { + stream: Some(stream), + }) + } + + fn write(&mut self, val: json::JsonValue) { + if let Some(stream) = &mut self.stream { + let mut buf = json::stringify(val); + buf.push('\n'); + if stream.write_all(buf.as_bytes()).is_err() { + self.stream = None; + } + } + } +} + +impl Progress for JSONProgress { + fn update(&mut self, counts: &StateCounts) { + self.write(json::object! { + counts: { + want: counts.get(BuildState::Want), + ready: counts.get(BuildState::Ready), + queued: counts.get(BuildState::Queued), + running: counts.get(BuildState::Running), + done: counts.get(BuildState::Done), + failed: counts.get(BuildState::Failed), + } + }); + } + + fn flush(&mut self) {} + + fn task_state(&mut self, id: BuildId, _build: &Build, _result: Option<&TaskResult>) { + self.write(json::object! { + task: { + id: id.index(), + } + }); + } + + fn finish(&mut self) { + // TODO a build finish()es multiple times due to updating build.ninja, + // so this isn't so useful for giving a status message. + } +} diff --git a/src/lib.rs b/src/lib.rs index 6bc52a1..de946cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ mod densemap; mod depfile; mod eval; mod graph; +mod json_progress; mod load; mod parse; mod progress; diff --git a/src/progress.rs b/src/progress.rs index 79a25ef..ae73ee0 100644 --- a/src/progress.rs +++ b/src/progress.rs @@ -68,6 +68,40 @@ pub trait Progress { fn finish(&mut self); } +pub struct MultiProgress { + progress: Vec>, +} +impl MultiProgress { + pub fn new(progress: Vec>) -> Self { + MultiProgress { progress } + } +} +impl Progress for MultiProgress { + fn update(&mut self, counts: &StateCounts) { + for p in self.progress.iter_mut() { + p.update(counts); + } + } + + fn flush(&mut self) { + for p in self.progress.iter_mut() { + p.flush(); + } + } + + fn task_state(&mut self, id: BuildId, build: &Build, result: Option<&TaskResult>) { + for p in self.progress.iter_mut() { + p.task_state(id, build, result); + } + } + + fn finish(&mut self) { + for p in self.progress.iter_mut() { + p.finish(); + } + } +} + /// Currently running build task, as tracked for progress updates. struct Task { id: BuildId, diff --git a/src/run.rs b/src/run.rs index ebf2115..e314278 100644 --- a/src/run.rs +++ b/src/run.rs @@ -3,7 +3,11 @@ extern crate getopts; use anyhow::anyhow; use std::path::Path; -use crate::{load, progress::ConsoleProgress, trace, work}; +use crate::json_progress::JSONProgress; +use crate::load; +use crate::progress::{ConsoleProgress, MultiProgress, Progress}; +use crate::trace; +use crate::work; // The result of starting a build. enum BuildResult { @@ -28,7 +32,7 @@ struct BuildParams<'a> { // possible, and if that build changes build.ninja, then return // BuildResult::Regen to signal to the caller that we need to start the whole // build over. -fn build(progress: &mut ConsoleProgress, params: &BuildParams) -> anyhow::Result { +fn build(progress: &mut dyn Progress, params: &BuildParams) -> anyhow::Result { let mut state = trace::scope("load::read", || load::read(params.build_filename))?; let mut work = work::Work::new( @@ -193,7 +197,15 @@ fn run_impl() -> anyhow::Result { build_filename = name; } - let mut progress = ConsoleProgress::new(matches.opt_present("v"), use_fancy_terminal()); + let mut progress: Box = Box::new(ConsoleProgress::new( + matches.opt_present("v"), + use_fancy_terminal(), + )); + + if let Ok(stream) = std::env::var("N2_PROGRESS_STREAM") { + let json_progress = Box::new(JSONProgress::new(&stream)?); + progress = Box::new(MultiProgress::new(vec![progress, json_progress])); + } // Build once with regen=true, and if the result says we regenerated the // build file, reload and build everything a second time. @@ -205,10 +217,10 @@ fn run_impl() -> anyhow::Result { target_names: &matches.free, build_filename: &build_filename, }; - let mut result = build(&mut progress, ¶ms)?; + let mut result = build(progress.as_mut(), ¶ms)?; if let BuildResult::Regen = result { params.regen = false; - result = build(&mut progress, ¶ms)?; + result = build(progress.as_mut(), ¶ms)?; } match result { diff --git a/vscode/README.md b/vscode/README.md deleted file mode 100644 index 41530da..0000000 --- a/vscode/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# n2 README - -This is the README for your extension "n2". After writing up a brief description, we recommend including the following sections. - -## Features - -Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file. - -For example if there is an image subfolder under your extension project workspace: - -\!\[feature X\]\(images/feature-x.png\) - -> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow. - -## Requirements - -If you have any requirements or dependencies, add a section describing those and how to install and configure them. - -## Extension Settings - -Include if your extension adds any VS Code settings through the `contributes.configuration` extension point. - -For example: - -This extension contributes the following settings: - -* `myExtension.enable`: enable/disable this extension -* `myExtension.thing`: set to `blah` to do something - -## Known Issues - -Calling out known issues can help limit users opening duplicate issues against your extension. - -## Release Notes - -Users appreciate release notes as you update your extension. - -### 1.0.0 - -Initial release of ... - -### 1.0.1 - -Fixed issue #. - -### 1.1.0 - -Added features X, Y, and Z. - ------------------------------------------------------------------------------------------------------------ -## Following extension guidelines - -Ensure that you've read through the extensions guidelines and follow the best practices for creating your extension. - -* [Extension Guidelines](https://code.visualstudio.com/api/references/extension-guidelines) - -## Working with Markdown - -**Note:** You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts: - -* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux) -* Toggle preview (`Shift+CMD+V` on macOS or `Shift+Ctrl+V` on Windows and Linux) -* Press `Ctrl+Space` (Windows, Linux) or `Cmd+Space` (macOS) to see a list of Markdown snippets - -### For more information - -* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown) -* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/) - -**Enjoy!** diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 926f28d..31d54b0 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -1,26 +1,12 @@ -// The module 'vscode' contains the VS Code extensibility API -// Import the module and reference it with the alias vscode in your code below import * as vscode from 'vscode'; -// this method is called when your extension is activated -// your extension is activated the very first time the command is executed export function activate(context: vscode.ExtensionContext) { - - // Use the console to output diagnostic information (console.log) and errors (console.error) - // This line of code will only be executed once when your extension is activated - console.log('Congratulations, your extension "n2" is now active!'); - // The command has been defined in the package.json file - // Now provide the implementation of the command with registerCommand - // The commandId parameter must match the command field in package.json let disposable = vscode.commands.registerCommand('n2.helloWorld', () => { - // The code you place here will be executed every time your command is executed - // Display a message box to the user vscode.window.showInformationMessage('Hello World from n2!'); }); context.subscriptions.push(disposable); } -// this method is called when your extension is deactivated export function deactivate() {}