Refactor source of Git commit build info (closes #661)

This commit is contained in:
Keavon Chambers
2022-05-24 14:33:58 -07:00
parent c73f8a64a0
commit eca9797597
17 changed files with 126 additions and 118 deletions
+22
View File
@@ -0,0 +1,22 @@
use std::process::Command;
const GRAPHITE_RELEASE_SERIES: &str = "Alpha Milestone 1";
fn main() {
// Execute a Git command for its stdout. Early exit if it fails for any of the possible reasons.
let try_git_command = |args: &[&str]| -> Option<String> {
let git_output = Command::new("git").args(args).output().ok()?;
let maybe_empty = String::from_utf8(git_output.stdout).ok()?;
let command_result = (!maybe_empty.is_empty()).then(|| maybe_empty)?;
Some(command_result)
};
// Execute a Git command for its output. Return "unknown" if it fails for any of the possible reasons.
let git_command = |args| -> String { try_git_command(args).unwrap_or_else(|| String::from("unknown")) };
// Rather than printing to any terminal, these commands set environment variables in the Cargo toolchain.
// They are accessed with the `env!("...")` macro in the codebase.
println!("cargo:rustc-env=GRAPHITE_GIT_COMMIT_DATE={}", git_command(&["log", "-1", "--format=%cd"]));
println!("cargo:rustc-env=GRAPHITE_GIT_COMMIT_HASH={}", git_command(&["rev-parse", "HEAD"]));
println!("cargo:rustc-env=GRAPHITE_GIT_COMMIT_BRANCH={}", git_command(&["rev-parse", "--abbrev-ref", "HEAD"]));
println!("cargo:rustc-env=GRAPHITE_RELEASE_SERIES={}", GRAPHITE_RELEASE_SERIES);
}
@@ -1,45 +0,0 @@
use serde::{Deserialize, Serialize};
/// Provides metadata about the build environment.
///
/// This data is viewable in the editor via the [`crate::dialog::AboutGraphite`] dialog.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct BuildMetadata {
pub release: String,
pub timestamp: String,
pub hash: String,
pub branch: String,
}
impl Default for BuildMetadata {
fn default() -> Self {
Self {
release: "unknown".to_string(),
timestamp: "unknown".to_string(),
hash: "unknown".to_string(),
branch: "unknown".to_string(),
}
}
}
impl BuildMetadata {
pub fn release_series(&self) -> String {
format!("Release Series: {}", self.release)
}
pub fn commit_info(&self) -> String {
format!("{}\n{}\n{}", self.commit_timestamp(), self.commit_hash(), self.commit_branch())
}
pub fn commit_timestamp(&self) -> String {
format!("Date: {}", self.timestamp)
}
pub fn commit_hash(&self) -> String {
format!("Hash: {}", self.hash)
}
pub fn commit_branch(&self) -> String {
format!("Branch: {}", self.branch)
}
}
+1 -7
View File
@@ -8,14 +8,11 @@ use crate::workspace::WorkspaceMessageHandler;
use std::collections::VecDeque; use std::collections::VecDeque;
use super::BuildMetadata;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Dispatcher { pub struct Dispatcher {
message_queue: VecDeque<Message>, message_queue: VecDeque<Message>,
pub responses: Vec<FrontendMessage>, pub responses: Vec<FrontendMessage>,
message_handlers: DispatcherMessageHandlers, message_handlers: DispatcherMessageHandlers,
build_metadata: BuildMetadata,
} }
#[remain::sorted] #[remain::sorted]
@@ -75,7 +72,7 @@ impl Dispatcher {
Dialog(message) => { Dialog(message) => {
self.message_handlers self.message_handlers
.dialog_message_handler .dialog_message_handler
.process_action(message, (&self.build_metadata, &self.message_handlers.portfolio_message_handler), &mut self.message_queue); .process_action(message, &self.message_handlers.portfolio_message_handler, &mut self.message_queue);
} }
Frontend(message) => { Frontend(message) => {
// Image and font loading should be immediately handled // Image and font loading should be immediately handled
@@ -120,9 +117,6 @@ impl Dispatcher {
.workspace_message_handler .workspace_message_handler
.process_action(message, &self.message_handlers.input_preprocessor_message_handler, &mut self.message_queue); .process_action(message, &self.message_handlers.input_preprocessor_message_handler, &mut self.message_queue);
} }
#[remain::unsorted]
PopulateBuildMetadata { new } => self.build_metadata = new,
} }
} }
} }
-4
View File
@@ -1,4 +1,3 @@
use super::BuildMetadata;
use crate::message_prelude::*; use crate::message_prelude::*;
use graphite_proc_macros::*; use graphite_proc_macros::*;
@@ -41,9 +40,6 @@ pub enum Message {
Tool(ToolMessage), Tool(ToolMessage),
#[child] #[child]
Workspace(WorkspaceMessage), Workspace(WorkspaceMessage),
#[remain::unsorted]
PopulateBuildMetadata { new: BuildMetadata },
} }
impl Message { impl Message {
-2
View File
@@ -1,11 +1,9 @@
mod build_metadata;
pub mod dispatcher; pub mod dispatcher;
pub mod message; pub mod message;
pub mod message_handler; pub mod message_handler;
pub use crate::communication::dispatcher::*; pub use crate::communication::dispatcher::*;
pub use crate::input::InputPreprocessorMessageHandler; pub use crate::input::InputPreprocessorMessageHandler;
pub use build_metadata::BuildMetadata;
use rand_chacha::rand_core::{RngCore, SeedableRng}; use rand_chacha::rand_core::{RngCore, SeedableRng};
use rand_chacha::ChaCha20Rng; use rand_chacha::ChaCha20Rng;
+5 -1
View File
@@ -7,14 +7,15 @@ use super::{ExportDialogUpdate, NewDocumentDialogUpdate};
#[impl_message(Message, Dialog)] #[impl_message(Message, Dialog)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)] #[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum DialogMessage { pub enum DialogMessage {
// Sub-messages
#[remain::unsorted] #[remain::unsorted]
#[child] #[child]
ExportDialog(ExportDialogUpdate), ExportDialog(ExportDialogUpdate),
#[remain::unsorted] #[remain::unsorted]
#[child] #[child]
NewDocumentDialog(NewDocumentDialogUpdate), NewDocumentDialog(NewDocumentDialogUpdate),
// Messages
CloseAllDocumentsWithConfirmation, CloseAllDocumentsWithConfirmation,
CloseDialogAndThen { CloseDialogAndThen {
followup: Box<Message>, followup: Box<Message>,
@@ -24,6 +25,9 @@ pub enum DialogMessage {
description: String, description: String,
}, },
RequestAboutGraphiteDialog, RequestAboutGraphiteDialog,
RequestAboutGraphiteDialogWithLocalizedCommitDate {
localized_commit_date: String,
},
RequestComingSoonDialog { RequestComingSoonDialog {
issue: Option<i32>, issue: Option<i32>,
}, },
+12 -6
View File
@@ -1,4 +1,3 @@
use crate::communication::BuildMetadata;
use crate::document::PortfolioMessageHandler; use crate::document::PortfolioMessageHandler;
use crate::layout::{layout_message::LayoutTarget, widgets::PropertyHolder}; use crate::layout::{layout_message::LayoutTarget, widgets::PropertyHolder};
use crate::message_prelude::*; use crate::message_prelude::*;
@@ -11,9 +10,9 @@ pub struct DialogMessageHandler {
new_document_dialog: NewDocument, new_document_dialog: NewDocument,
} }
impl MessageHandler<DialogMessage, (&BuildMetadata, &PortfolioMessageHandler)> for DialogMessageHandler { impl MessageHandler<DialogMessage, &PortfolioMessageHandler> for DialogMessageHandler {
#[remain::check] #[remain::check]
fn process_action(&mut self, message: DialogMessage, (build_metadata, portfolio): (&BuildMetadata, &PortfolioMessageHandler), responses: &mut VecDeque<Message>) { fn process_action(&mut self, message: DialogMessage, portfolio: &PortfolioMessageHandler, responses: &mut VecDeque<Message>) {
#[remain::sorted] #[remain::sorted]
match message { match message {
#[remain::unsorted] #[remain::unsorted]
@@ -36,9 +35,16 @@ impl MessageHandler<DialogMessage, (&BuildMetadata, &PortfolioMessageHandler)> f
responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into()); responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into());
} }
DialogMessage::RequestAboutGraphiteDialog => { DialogMessage::RequestAboutGraphiteDialog => {
let about_graphite = AboutGraphite { responses.push_back(
build_metadata: build_metadata.clone(), FrontendMessage::TriggerAboutGraphiteLocalizedCommitDate {
}; commit_date: env!("GRAPHITE_GIT_COMMIT_DATE").into(),
}
.into(),
);
}
DialogMessage::RequestAboutGraphiteDialogWithLocalizedCommitDate { localized_commit_date } => {
let about_graphite = AboutGraphite { localized_commit_date };
about_graphite.register_properties(responses, LayoutTarget::DialogDetails); about_graphite.register_properties(responses, LayoutTarget::DialogDetails);
responses.push_back(FrontendMessage::DisplayDialog { icon: "GraphiteLogo".to_string() }.into()); responses.push_back(FrontendMessage::DisplayDialog { icon: "GraphiteLogo".to_string() }.into());
} }
+7 -5
View File
@@ -1,8 +1,10 @@
use crate::{communication::BuildMetadata, layout::widgets::*, message_prelude::FrontendMessage}; use crate::layout::widgets::*;
use crate::message_prelude::FrontendMessage;
use crate::misc::build_metadata::{commit_info_localized, release_series};
/// A dialog for displaying information on [`BuildMetadata`] viewable via `help -> about graphite` in the menu bar. /// A dialog for displaying information on [`BuildMetadata`] viewable via *Help* > *About Graphite* in the menu bar.
pub struct AboutGraphite { pub struct AboutGraphite {
pub build_metadata: BuildMetadata, pub localized_commit_date: String,
} }
impl PropertyHolder for AboutGraphite { impl PropertyHolder for AboutGraphite {
@@ -33,13 +35,13 @@ impl PropertyHolder for AboutGraphite {
}, },
LayoutRow::Row { LayoutRow::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: self.build_metadata.release_series(), value: release_series(),
..Default::default() ..Default::default()
}))], }))],
}, },
LayoutRow::Row { LayoutRow::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel { widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: self.build_metadata.commit_info(), value: commit_info_localized(self.localized_commit_date.as_str()),
multiline: true, multiline: true,
..Default::default() ..Default::default()
}))], }))],
+1
View File
@@ -20,6 +20,7 @@ pub enum FrontendMessage {
DisplayRemoveEditableTextbox, DisplayRemoveEditableTextbox,
// Trigger prefix: cause a browser API to do something // Trigger prefix: cause a browser API to do something
TriggerAboutGraphiteLocalizedCommitDate { commit_date: String },
TriggerFileDownload { document: String, name: String }, TriggerFileDownload { document: String, name: String },
TriggerFileUpload, TriggerFileUpload,
TriggerFontLoad { font_file_url: String }, TriggerFontLoad { font_file_url: String },
+31
View File
@@ -0,0 +1,31 @@
/// Provides metadata about the build environment.
///
/// This data is viewable in the editor via the [`crate::dialog::AboutGraphite`] dialog.
pub fn release_series() -> String {
format!("Release Series: {}", env!("GRAPHITE_RELEASE_SERIES"))
}
pub fn commit_info() -> String {
format!("{}\n{}\n{}", commit_timestamp(), commit_hash(), commit_branch())
}
pub fn commit_info_localized(localized_commit_date: &str) -> String {
format!("{}\n{}\n{}", commit_timestamp_localized(localized_commit_date), commit_hash(), commit_branch())
}
pub fn commit_timestamp() -> String {
format!("Date: {}", env!("GRAPHITE_GIT_COMMIT_DATE"))
}
pub fn commit_timestamp_localized(localized_commit_date: &str) -> String {
format!("Date: {}", localized_commit_date)
}
pub fn commit_hash() -> String {
format!("Hash: {}", &env!("GRAPHITE_GIT_COMMIT_HASH")[..8])
}
pub fn commit_branch() -> String {
format!("Branch: {}", env!("GRAPHITE_GIT_COMMIT_BRANCH"))
}
+4 -3
View File
@@ -1,11 +1,12 @@
#[macro_use] #[macro_use]
pub mod macros; pub mod macros;
pub mod build_metadata;
pub mod derivable_custom_traits; pub mod derivable_custom_traits;
pub mod hints; pub mod hints;
pub mod test_utils; pub mod test_utils;
mod error;
pub use error::EditorError; pub use error::EditorError;
pub use hints::*; pub use hints::*;
pub use macros::*; pub use macros::*;
mod error;
+3 -3
View File
@@ -258,10 +258,10 @@ img {
<script lang="ts"> <script lang="ts">
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { createBuildMetadataManager } from "@/io-managers/build-metadata";
import { createClipboardManager } from "@/io-managers/clipboard"; import { createClipboardManager } from "@/io-managers/clipboard";
import { createHyperlinkManager } from "@/io-managers/hyperlinks"; import { createHyperlinkManager } from "@/io-managers/hyperlinks";
import { createInputManager } from "@/io-managers/input"; import { createInputManager } from "@/io-managers/input";
import { createLocalizationManager } from "@/io-managers/localization";
import { createPanicManager } from "@/io-managers/panic"; import { createPanicManager } from "@/io-managers/panic";
import { createPersistenceManager } from "@/io-managers/persistence"; import { createPersistenceManager } from "@/io-managers/persistence";
import { createDialogState, DialogState } from "@/state-providers/dialog"; import { createDialogState, DialogState } from "@/state-providers/dialog";
@@ -276,10 +276,10 @@ import LayoutRow from "@/components/layout/LayoutRow.vue";
import MainWindow from "@/components/window/MainWindow.vue"; import MainWindow from "@/components/window/MainWindow.vue";
const managerDestructors: { const managerDestructors: {
createBuildMetadataManager?: () => void;
createClipboardManager?: () => void; createClipboardManager?: () => void;
createHyperlinkManager?: () => void; createHyperlinkManager?: () => void;
createInputManager?: () => void; createInputManager?: () => void;
createLocalizationManager?: () => void;
createPanicManager?: () => void; createPanicManager?: () => void;
createPersistenceManager?: () => void; createPersistenceManager?: () => void;
} = {}; } = {};
@@ -332,10 +332,10 @@ export default defineComponent({
async mounted() { async mounted() {
// Initialize managers, which are isolated systems that subscribe to backend messages to link them to browser API functionality (like JS events, IndexedDB, etc.) // Initialize managers, which are isolated systems that subscribe to backend messages to link them to browser API functionality (like JS events, IndexedDB, etc.)
Object.assign(managerDestructors, { Object.assign(managerDestructors, {
createBuildMetadataManager: createBuildMetadataManager(this.editor),
createClipboardManager: createClipboardManager(this.editor), createClipboardManager: createClipboardManager(this.editor),
createHyperlinkManager: createHyperlinkManager(this.editor), createHyperlinkManager: createHyperlinkManager(this.editor),
createInputManager: createInputManager(this.editor, this.$el.parentElement, this.dialog, this.portfolio, this.fullscreen), createInputManager: createInputManager(this.editor, this.$el.parentElement, this.dialog, this.portfolio, this.fullscreen),
createLocalizationManager: createLocalizationManager(this.editor),
createPanicManager: createPanicManager(this.editor, this.dialog), createPanicManager: createPanicManager(this.editor, this.dialog),
createPersistenceManager: await createPersistenceManager(this.editor, this.portfolio), createPersistenceManager: await createPersistenceManager(this.editor, this.portfolio),
}); });
@@ -1,26 +0,0 @@
import { Editor } from "@/wasm-communication/editor";
// Gets metadata populated in the `process.env` namespace by code in `frontend/vue.config.js`.
// TODO: Move that functionality to a build.rs file so our web build system is more lightweight.
export function createBuildMetadataManager(editor: Editor): void {
// Release
const release = process.env.VUE_APP_RELEASE_SERIES;
// Timestamp
const date = new Date(process.env.VUE_APP_COMMIT_DATE || "");
const timezoneName = Intl.DateTimeFormat(undefined, { timeZoneName: "long" })
.formatToParts(new Date())
.find((part) => part.type === "timeZoneName");
const dateString = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
const timeString = `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
const timezoneNameString = timezoneName?.value;
const timestamp = `${dateString} ${timeString} ${timezoneNameString}`;
// Hash
const hash = (process.env.VUE_APP_COMMIT_HASH || "").substring(0, 8);
// Branch
const branch = process.env.VUE_APP_COMMIT_BRANCH;
editor.instance.populate_build_metadata(release || "", timestamp, hash, branch || "");
}
+25
View File
@@ -0,0 +1,25 @@
import { Editor } from "@/wasm-communication/editor";
import { TriggerAboutGraphiteLocalizedCommitDate } from "@/wasm-communication/messages";
export function createLocalizationManager(editor: Editor): void {
function localizeTimestamp(utc: string): string {
// Timestamp
const date = new Date(utc);
if (Number.isNaN(date.getTime())) return utc;
const timezoneName = Intl.DateTimeFormat(undefined, { timeZoneName: "long" })
.formatToParts(new Date())
.find((part) => part.type === "timeZoneName");
const dateString = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
const timeString = `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
const timezoneNameString = timezoneName?.value;
return `${dateString} ${timeString} ${timezoneNameString}`;
}
// Subscribe to process backend event
editor.subscriptions.subscribeJsMessage(TriggerAboutGraphiteLocalizedCommitDate, (triggerAboutGraphiteLocalizedCommitDate) => {
const localized = localizeTimestamp(triggerAboutGraphiteLocalizedCommitDate.commit_date);
editor.instance.request_about_graphite_dialog_with_localized_commit_date(localized);
});
}
@@ -522,6 +522,10 @@ export class TriggerTextCopy extends JsMessage {
readonly copy_text!: string; readonly copy_text!: string;
} }
export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage {
readonly commit_date!: string;
}
export class TriggerViewportResize extends JsMessage {} export class TriggerViewportResize extends JsMessage {}
// `any` is used since the type of the object should be known from the Rust side // `any` is used since the type of the object should be known from the Rust side
@@ -546,6 +550,7 @@ export const messageMakers: Record<string, MessageMaker> = {
TriggerRasterDownload, TriggerRasterDownload,
TriggerTextCommit, TriggerTextCommit,
TriggerTextCopy, TriggerTextCopy,
TriggerAboutGraphiteLocalizedCommitDate,
TriggerViewportResize, TriggerViewportResize,
TriggerVisitLink, TriggerVisitLink,
UpdateActiveDocument, UpdateActiveDocument,
+6 -11
View File
@@ -1,15 +1,10 @@
/* eslint-disable @typescript-eslint/no-var-requires, no-console */ /* eslint-disable @typescript-eslint/no-var-requires, no-console */
const { execSync, spawnSync } = require("child_process"); const { spawnSync } = require("child_process");
const path = require("path"); const path = require("path");
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin"); const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
const LicenseCheckerWebpackPlugin = require("license-checker-webpack-plugin"); const LicenseCheckerWebpackPlugin = require("license-checker-webpack-plugin");
process.env.VUE_APP_COMMIT_DATE = execSync("git log -1 --format=%cd", { encoding: "utf-8" }).trim();
process.env.VUE_APP_COMMIT_HASH = execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
process.env.VUE_APP_COMMIT_BRANCH = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
process.env.VUE_APP_RELEASE_SERIES = "Alpha Milestone 1";
module.exports = { module.exports = {
lintOnSave: "warning", lintOnSave: "warning",
// https://cli.vuejs.org/guide/webpack.html // https://cli.vuejs.org/guide/webpack.html
@@ -91,7 +86,7 @@ function formatThirdPartyLicenses(jsLicenses) {
if (rustLicenses === null) { if (rustLicenses === null) {
// This is probably caused by cargo about not being installed // This is probably caused by cargo about not being installed
console.error(` console.error(`
Could not run 'cargo about', which is required to generate license information. Could not run \`cargo about\`, which is required to generate license information.
To install cargo-about on your system, you can run: To install cargo-about on your system, you can run:
cargo install cargo-about cargo install cargo-about
License information is required on production builds. Aborting.`); License information is required on production builds. Aborting.`);
@@ -178,18 +173,18 @@ ${license.licenseText}
} }
function generateRustLicenses() { function generateRustLicenses() {
console.info("Generating license information for rust code"); console.info("Generating license information for Rust code");
const { stdout, stderr, status } = spawnSync("cargo", ["about", "generate", "about.hbs"], { const { stdout, stderr, status } = spawnSync("cargo", ["about", "generate", "about.hbs"], {
cwd: path.join(__dirname, ".."), cwd: path.join(__dirname, ".."),
encoding: "utf8", encoding: "utf8",
timeout: 60000, // one minute timeout: 60000, // One minute
shell: true, shell: true,
windowsHide: true, // hide the DOS window on windows windowsHide: true, // Hide the terminal on Windows
}); });
if (status !== 0) { if (status !== 0) {
if (status !== 101) { if (status !== 101) {
// cargo returns 101 when the subcommand wasn't found // Cargo returns 101 when the subcommand wasn't found
console.error("cargo-about failed", status, stderr); console.error("cargo-about failed", status, stderr);
} }
return null; return null;
+4 -5
View File
@@ -239,14 +239,13 @@ impl JsEditorHandle {
self.dispatch(message); self.dispatch(message);
} }
pub fn populate_build_metadata(&self, release: String, timestamp: String, hash: String, branch: String) { pub fn request_about_graphite_dialog(&self) {
let new = editor::communication::BuildMetadata { release, timestamp, hash, branch }; let message = DialogMessage::RequestAboutGraphiteDialog;
let message = Message::PopulateBuildMetadata { new };
self.dispatch(message); self.dispatch(message);
} }
pub fn request_about_graphite_dialog(&self) { pub fn request_about_graphite_dialog_with_localized_commit_date(&self, localized_commit_date: String) {
let message = DialogMessage::RequestAboutGraphiteDialog; let message = DialogMessage::RequestAboutGraphiteDialogWithLocalizedCommitDate { localized_commit_date };
self.dispatch(message); self.dispatch(message);
} }