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 b624405b0c
commit 8b94c62697
17 changed files with 126 additions and 118 deletions

22
editor/build.rs Normal file
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);
}

View File

@ -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)
}
}

View File

@ -8,14 +8,11 @@ use crate::workspace::WorkspaceMessageHandler;
use std::collections::VecDeque;
use super::BuildMetadata;
#[derive(Debug, Default)]
pub struct Dispatcher {
message_queue: VecDeque<Message>,
pub responses: Vec<FrontendMessage>,
message_handlers: DispatcherMessageHandlers,
build_metadata: BuildMetadata,
}
#[remain::sorted]
@ -75,7 +72,7 @@ impl Dispatcher {
Dialog(message) => {
self.message_handlers
.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) => {
// Image and font loading should be immediately handled
@ -120,9 +117,6 @@ impl Dispatcher {
.workspace_message_handler
.process_action(message, &self.message_handlers.input_preprocessor_message_handler, &mut self.message_queue);
}
#[remain::unsorted]
PopulateBuildMetadata { new } => self.build_metadata = new,
}
}
}

View File

@ -1,4 +1,3 @@
use super::BuildMetadata;
use crate::message_prelude::*;
use graphite_proc_macros::*;
@ -41,9 +40,6 @@ pub enum Message {
Tool(ToolMessage),
#[child]
Workspace(WorkspaceMessage),
#[remain::unsorted]
PopulateBuildMetadata { new: BuildMetadata },
}
impl Message {

View File

@ -1,11 +1,9 @@
mod build_metadata;
pub mod dispatcher;
pub mod message;
pub mod message_handler;
pub use crate::communication::dispatcher::*;
pub use crate::input::InputPreprocessorMessageHandler;
pub use build_metadata::BuildMetadata;
use rand_chacha::rand_core::{RngCore, SeedableRng};
use rand_chacha::ChaCha20Rng;

View File

@ -7,14 +7,15 @@ use super::{ExportDialogUpdate, NewDocumentDialogUpdate};
#[impl_message(Message, Dialog)]
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
pub enum DialogMessage {
// Sub-messages
#[remain::unsorted]
#[child]
ExportDialog(ExportDialogUpdate),
#[remain::unsorted]
#[child]
NewDocumentDialog(NewDocumentDialogUpdate),
// Messages
CloseAllDocumentsWithConfirmation,
CloseDialogAndThen {
followup: Box<Message>,
@ -24,6 +25,9 @@ pub enum DialogMessage {
description: String,
},
RequestAboutGraphiteDialog,
RequestAboutGraphiteDialogWithLocalizedCommitDate {
localized_commit_date: String,
},
RequestComingSoonDialog {
issue: Option<i32>,
},

View File

@ -1,4 +1,3 @@
use crate::communication::BuildMetadata;
use crate::document::PortfolioMessageHandler;
use crate::layout::{layout_message::LayoutTarget, widgets::PropertyHolder};
use crate::message_prelude::*;
@ -11,9 +10,9 @@ pub struct DialogMessageHandler {
new_document_dialog: NewDocument,
}
impl MessageHandler<DialogMessage, (&BuildMetadata, &PortfolioMessageHandler)> for DialogMessageHandler {
impl MessageHandler<DialogMessage, &PortfolioMessageHandler> for DialogMessageHandler {
#[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]
match message {
#[remain::unsorted]
@ -36,9 +35,16 @@ impl MessageHandler<DialogMessage, (&BuildMetadata, &PortfolioMessageHandler)> f
responses.push_back(FrontendMessage::DisplayDialog { icon: "Warning".to_string() }.into());
}
DialogMessage::RequestAboutGraphiteDialog => {
let about_graphite = AboutGraphite {
build_metadata: build_metadata.clone(),
};
responses.push_back(
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);
responses.push_back(FrontendMessage::DisplayDialog { icon: "GraphiteLogo".to_string() }.into());
}

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 build_metadata: BuildMetadata,
pub localized_commit_date: String,
}
impl PropertyHolder for AboutGraphite {
@ -33,13 +35,13 @@ impl PropertyHolder for AboutGraphite {
},
LayoutRow::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: self.build_metadata.release_series(),
value: release_series(),
..Default::default()
}))],
},
LayoutRow::Row {
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,
..Default::default()
}))],

View File

@ -20,6 +20,7 @@ pub enum FrontendMessage {
DisplayRemoveEditableTextbox,
// Trigger prefix: cause a browser API to do something
TriggerAboutGraphiteLocalizedCommitDate { commit_date: String },
TriggerFileDownload { document: String, name: String },
TriggerFileUpload,
TriggerFontLoad { font_file_url: String },

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"))
}

View File

@ -1,11 +1,12 @@
#[macro_use]
pub mod macros;
pub mod build_metadata;
pub mod derivable_custom_traits;
pub mod hints;
pub mod test_utils;
mod error;
pub use error::EditorError;
pub use hints::*;
pub use macros::*;
mod error;

View File

@ -258,10 +258,10 @@ img {
<script lang="ts">
import { defineComponent } from "vue";
import { createBuildMetadataManager } from "@/io-managers/build-metadata";
import { createClipboardManager } from "@/io-managers/clipboard";
import { createHyperlinkManager } from "@/io-managers/hyperlinks";
import { createInputManager } from "@/io-managers/input";
import { createLocalizationManager } from "@/io-managers/localization";
import { createPanicManager } from "@/io-managers/panic";
import { createPersistenceManager } from "@/io-managers/persistence";
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";
const managerDestructors: {
createBuildMetadataManager?: () => void;
createClipboardManager?: () => void;
createHyperlinkManager?: () => void;
createInputManager?: () => void;
createLocalizationManager?: () => void;
createPanicManager?: () => void;
createPersistenceManager?: () => void;
} = {};
@ -332,10 +332,10 @@ export default defineComponent({
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.)
Object.assign(managerDestructors, {
createBuildMetadataManager: createBuildMetadataManager(this.editor),
createClipboardManager: createClipboardManager(this.editor),
createHyperlinkManager: createHyperlinkManager(this.editor),
createInputManager: createInputManager(this.editor, this.$el.parentElement, this.dialog, this.portfolio, this.fullscreen),
createLocalizationManager: createLocalizationManager(this.editor),
createPanicManager: createPanicManager(this.editor, this.dialog),
createPersistenceManager: await createPersistenceManager(this.editor, this.portfolio),
});

View File

@ -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 || "");
}

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);
});
}

View File

@ -522,6 +522,10 @@ export class TriggerTextCopy extends JsMessage {
readonly copy_text!: string;
}
export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage {
readonly commit_date!: string;
}
export class TriggerViewportResize extends JsMessage {}
// `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,
TriggerTextCommit,
TriggerTextCopy,
TriggerAboutGraphiteLocalizedCommitDate,
TriggerViewportResize,
TriggerVisitLink,
UpdateActiveDocument,

View File

@ -1,15 +1,10 @@
/* 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 WasmPackPlugin = require("@wasm-tool/wasm-pack-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 = {
lintOnSave: "warning",
// https://cli.vuejs.org/guide/webpack.html
@ -91,7 +86,7 @@ function formatThirdPartyLicenses(jsLicenses) {
if (rustLicenses === null) {
// This is probably caused by cargo about not being installed
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:
cargo install cargo-about
License information is required on production builds. Aborting.`);
@ -178,18 +173,18 @@ ${license.licenseText}
}
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"], {
cwd: path.join(__dirname, ".."),
encoding: "utf8",
timeout: 60000, // one minute
timeout: 60000, // One minute
shell: true,
windowsHide: true, // hide the DOS window on windows
windowsHide: true, // Hide the terminal on Windows
});
if (status !== 0) {
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);
}
return null;

View File

@ -239,14 +239,13 @@ impl JsEditorHandle {
self.dispatch(message);
}
pub fn populate_build_metadata(&self, release: String, timestamp: String, hash: String, branch: String) {
let new = editor::communication::BuildMetadata { release, timestamp, hash, branch };
let message = Message::PopulateBuildMetadata { new };
pub fn request_about_graphite_dialog(&self) {
let message = DialogMessage::RequestAboutGraphiteDialog;
self.dispatch(message);
}
pub fn request_about_graphite_dialog(&self) {
let message = DialogMessage::RequestAboutGraphiteDialog;
pub fn request_about_graphite_dialog_with_localized_commit_date(&self, localized_commit_date: String) {
let message = DialogMessage::RequestAboutGraphiteDialogWithLocalizedCommitDate { localized_commit_date };
self.dispatch(message);
}