From 92c08a00cea2397d63ec6f35199cdddebc5aad83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=2E=20Urba=C5=84czyk?= Date: Sun, 17 Sep 2023 16:34:16 +0200 Subject: [PATCH 01/29] wip --- Cargo.lock | 9 + Cargo.toml | 1 + app/gui2/package-lock.json | 245 +++++++++++++++++- app/gui2/package.json | 4 +- app/gui2/rust-ffi/Cargo.toml | 18 +- app/gui2/tsconfig.node.json | 1 + app/gui2/vite.config.ts | 3 +- .../lib/client/electron-builder-config.ts | 18 +- build/build/paths.yaml | 5 + build/build/src/ide/web.rs | 7 +- build/build/src/project.rs | 2 + build/build/src/project/gui2.rs | 161 ++++++++++++ build/build/src/project/ide.rs | 36 ++- build/build/src/project/ide2.rs | 4 + build/cli/src/arg.rs | 10 +- build/cli/src/arg/gui2.rs | 30 +++ build/cli/src/arg/ide2.rs | 78 ++++++ build/cli/src/lib.rs | 73 +++++- 18 files changed, 673 insertions(+), 32 deletions(-) create mode 100644 build/build/src/project/gui2.rs create mode 100644 build/build/src/project/ide2.rs create mode 100644 build/cli/src/arg/gui2.rs create mode 100644 build/cli/src/arg/ide2.rs diff --git a/Cargo.lock b/Cargo.lock index cdeb2423842d..fd6e57f8e289 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5995,6 +5995,15 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rust-ffi" +version = "0.1.0" +dependencies = [ + "enso-parser", + "serde_json", + "wasm-bindgen", +] + [[package]] name = "rustc-demangle" version = "0.1.21" diff --git a/Cargo.toml b/Cargo.toml index 2bcc2ed35f14..01b79cc2d391 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "app/gui", "app/gui/language/parser", "app/gui/enso-profiler-enso-data", + "app/gui2/rust-ffi", "build/cli", "build/macros/proc-macro", "build/ci-gen", diff --git a/app/gui2/package-lock.json b/app/gui2/package-lock.json index 1c9c5703b430..61bf724336c2 100644 --- a/app/gui2/package-lock.json +++ b/app/gui2/package-lock.json @@ -7,7 +7,6 @@ "": { "name": "enso-ide", "version": "0.0.0", - "hasInstallScript": true, "dependencies": { "@vueuse/core": "^10.4.1", "lib0": "^0.2.83", @@ -39,6 +38,7 @@ "prettier": "^3.0.0", "typescript": "~5.1.6", "vite": "^4.4.9", + "vite-plugin-top-level-await": "^1.3.1", "vitest": "^0.34.2", "vue-tsc": "^1.8.8" } @@ -622,6 +622,23 @@ "fsevents": "2.3.2" } }, + "node_modules/@rollup/plugin-virtual": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.1.tgz", + "integrity": "sha512-fK8O0IL5+q+GrsMLuACVNk2x21g3yaw+sG2qn16SnUd3IlBsQyvWxLMGHmCmXRMecPjGRSZ/1LmZB4rjQm68og==", + "dev": true, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.3.tgz", @@ -634,6 +651,209 @@ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, + "node_modules/@swc/core": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.83.tgz", + "integrity": "sha512-PccHDgGQlFjpExgJxH91qA3a4aifR+axCFJ4RieCoiI0m5gURE4nBhxzTBY5YU/YKTBmPO8Gc5Q6inE3+NquWg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/types": "^0.1.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.83", + "@swc/core-darwin-x64": "1.3.83", + "@swc/core-linux-arm-gnueabihf": "1.3.83", + "@swc/core-linux-arm64-gnu": "1.3.83", + "@swc/core-linux-arm64-musl": "1.3.83", + "@swc/core-linux-x64-gnu": "1.3.83", + "@swc/core-linux-x64-musl": "1.3.83", + "@swc/core-win32-arm64-msvc": "1.3.83", + "@swc/core-win32-ia32-msvc": "1.3.83", + "@swc/core-win32-x64-msvc": "1.3.83" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.83.tgz", + "integrity": "sha512-Plz2IKeveVLivbXTSCC3OZjD2MojyKYllhPrn9RotkDIZEFRYJZtW5/Ik1tJW/2rzu5HVKuGYrDKdScVVTbOxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.83.tgz", + "integrity": "sha512-FBGVg5IPF/8jQ6FbK60iDUHjv0H5+LwfpJHKH6wZnRaYWFtm7+pzYgreLu3NTsm3m7/1a7t0+7KURwBGUaJCCw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.83.tgz", + "integrity": "sha512-EZcsuRYhGkzofXtzwDjuuBC/suiX9s7zeg2YYXOVjWwyebb6BUhB1yad3mcykFQ20rTLO9JUyIaiaMYDHGobqw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.83.tgz", + "integrity": "sha512-khI41szLHrCD/cFOcN4p2SYvZgHjhhHlcMHz5BksRrDyteSJKu0qtWRZITVom0N/9jWoAleoFhMnFTUs0H8IWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.83.tgz", + "integrity": "sha512-zgT7yNOdbjHcGAwvys79mbfNLK65KBlPJWzeig+Yk7I8TVzmaQge7B6ZS/gwF9/p+8TiLYo/tZ5aF2lqlgdSVw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.83.tgz", + "integrity": "sha512-x+mH0Y3NC/G0YNlFmGi3vGD4VOm7IPDhh+tGrx6WtJp0BsShAbOpxtfU885rp1QweZe4qYoEmGqiEjE2WrPIdA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.83.tgz", + "integrity": "sha512-s5AYhAOmetUwUZwS5g9qb92IYgNHHBGiY2mTLImtEgpAeBwe0LPDj6WrujxCBuZnaS55mKRLLOuiMZE5TpjBNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.83.tgz", + "integrity": "sha512-yw2rd/KVOGs95lRRB+killLWNaO1dy4uVa8Q3/4wb5txlLru07W1m041fZLzwOg/1Sh0TMjJgGxj0XHGR3ZXhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.83.tgz", + "integrity": "sha512-POW+rgZ6KWqBpwPGIRd2/3pcf46P+UrKBm4HLt5IwbHvekJ4avIM8ixJa9kK0muJNVJcDpaZgxaU1ELxtJ1j8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.83", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.83.tgz", + "integrity": "sha512-CiWQtkFnZElXQUalaHp+Wacw0Jd+24ncRYhqaJ9YKnEQP1H82CxIIuQqLM8IFaLpn5dpY6SgzaeubWF46hjcLA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/types": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.4.tgz", + "integrity": "sha512-z/G02d+59gyyUb7KYhKi9jOhicek6QD2oMaotUyG+lUkybpXoV49dY9bj7Ah5Q+y7knK2jU67UTX9FyfGzaxQg==", + "dev": true + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -5736,6 +5956,15 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -5824,6 +6053,20 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-plugin-top-level-await": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.3.1.tgz", + "integrity": "sha512-55M1h4NAwkrpxPNOJIBzKZFihqLUzIgnElLSmPNPMR2Fn9+JHKaNg3sVX1Fq+VgvuBksQYxiD3OnwQAUu7kaPQ==", + "dev": true, + "dependencies": { + "@rollup/plugin-virtual": "^3.0.1", + "@swc/core": "^1.3.10", + "uuid": "^9.0.0" + }, + "peerDependencies": { + "vite": ">=2.8" + } + }, "node_modules/vitest": { "version": "0.34.2", "resolved": "https://registry.npmjs.org/vitest/-/vitest-0.34.2.tgz", diff --git a/app/gui2/package.json b/app/gui2/package.json index f2d7413bd93b..4112a5422ce0 100644 --- a/app/gui2/package.json +++ b/app/gui2/package.json @@ -12,8 +12,7 @@ "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false", "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", "format": "prettier --write src/", - "build-rust-ffi": "cd rust-ffi && wasm-pack build --release --target web", - "preinstall": "npm run build-rust-ffi" + "build-rust-ffi": "cd rust-ffi && wasm-pack build --release --target web" }, "dependencies": { "@vueuse/core": "^10.4.1", @@ -46,6 +45,7 @@ "prettier": "^3.0.0", "typescript": "~5.1.6", "vite": "^4.4.9", + "vite-plugin-top-level-await": "^1.3.1", "vitest": "^0.34.2", "vue-tsc": "^1.8.8" } diff --git a/app/gui2/rust-ffi/Cargo.toml b/app/gui2/rust-ffi/Cargo.toml index 715857a023af..1f7e6b657a3c 100644 --- a/app/gui2/rust-ffi/Cargo.toml +++ b/app/gui2/rust-ffi/Cargo.toml @@ -8,18 +8,16 @@ authors = ["Enso Team "] crate-type = ["cdylib", "rlib"] [dependencies] -wasm-bindgen = { version = "0.2.84", features = [] } enso-parser = { path = "../../../lib/rust/parser" } -serde_json = "1.0" +serde_json = { workspace = true } +wasm-bindgen = { workspace = true } -[workspace] - -[profile.release] -debug = false -strip = true -lto = true -codegen-units = 1 -opt-level = "z" +#[profile.release] +#debug = false +#strip = true +#lto = true +#codegen-units = 1 +#opt-level = "z" [package.metadata.wasm-pack.profile.release] wasm-opt = ['-Os'] diff --git a/app/gui2/tsconfig.node.json b/app/gui2/tsconfig.node.json index 5c20c33c1aed..06570da87dec 100644 --- a/app/gui2/tsconfig.node.json +++ b/app/gui2/tsconfig.node.json @@ -5,6 +5,7 @@ "composite": true, "module": "ESNext", "moduleResolution": "Bundler", + "target": "ESNext", "types": ["node"] } } diff --git a/app/gui2/vite.config.ts b/app/gui2/vite.config.ts index 8a76a7b3b2c8..636ed821d974 100644 --- a/app/gui2/vite.config.ts +++ b/app/gui2/vite.config.ts @@ -4,10 +4,11 @@ import { defineConfig, Plugin } from 'vite' import vue from '@vitejs/plugin-vue' import postcssNesting from 'postcss-nesting' import { WebSocketServer } from 'ws' +import topLevelAwait from 'vite-plugin-top-level-await' // https://vitejs.dev/config/ export default defineConfig({ - plugins: [vue(), yWebsocketServer()], + plugins: [topLevelAwait(), vue(), yWebsocketServer()], resolve: { alias: { shared: fileURLToPath(new URL('./shared', import.meta.url)), diff --git a/app/ide-desktop/lib/client/electron-builder-config.ts b/app/ide-desktop/lib/client/electron-builder-config.ts index e0efd2b4c182..9b1b8f6111fd 100644 --- a/app/ide-desktop/lib/client/electron-builder-config.ts +++ b/app/ide-desktop/lib/client/electron-builder-config.ts @@ -92,14 +92,24 @@ export const args: Arguments = await yargs(process.argv.slice(2)) // ====================================== /** Based on the given arguments, creates a configuration for the Electron Builder. */ -export function createElectronBuilderConfig(passedArgs: Arguments): electronBuilder.Configuration { +export async function createElectronBuilderConfig( + passedArgs: Arguments +): Promise { + // Check if source GUI directory has assets subdirectory. + // const sourceGuiAssets = path.join(passedArgs.guiDist, 'assets') + // const doesGuiHaveAssets = (await fs.stat(sourceGuiAssets)).isDirectory() + const doesGuiHaveAssets = true + // The Rust-based GUI ships assets directory which is then copied to the IDE. The vue-based GUI goes without it, + // so to make the build work for both versions we place the files in the assets directory by hand. + const guiDestination = doesGuiHaveAssets ? '.' : './assets' + return { appId: 'org.enso', productName: common.PRODUCT_NAME, extraMetadata: { version: BUILD_INFO.version, }, - copyright: 'Copyright © 2022 ${author}.', + copyright: `Copyright © ${new Date(). getFullYear()} $\{author}.`, artifactName: 'enso-${os}-${version}.${ext}', /** Definitions of URL {@link electronBuilder.Protocol} schemes used by the IDE. * @@ -161,7 +171,7 @@ export function createElectronBuilderConfig(passedArgs: Arguments): electronBuil }, files: [ '!**/node_modules/**/*', - { from: `${passedArgs.guiDist}/`, to: '.' }, + { from: `${passedArgs.guiDist}/`, to: guiDestination }, { from: `${passedArgs.ideDist}/client`, to: '.' }, ], extraResources: [ @@ -285,7 +295,7 @@ export async function buildPackage(passedArgs: Arguments) { await fs.mkdir('node_modules', { recursive: true }) const cliOpts: electronBuilder.CliOptions = { - config: createElectronBuilderConfig(passedArgs), + config: await createElectronBuilderConfig(passedArgs), targets: passedArgs.platform.createTarget(), } console.log('Building with configuration:', cliOpts) diff --git a/build/build/paths.yaml b/build/build/paths.yaml index e0a319d4a14d..8dd44740312d 100644 --- a/build/build/paths.yaml +++ b/build/build/paths.yaml @@ -15,6 +15,8 @@ shader-tools.yml: app/: gui/: + gui2/: # The new, Vue-based GUI. + dist/: ide-desktop/: lib/: client/: @@ -58,6 +60,9 @@ style.css: ensogl-app/: + gui2/: + assets/: + # Final WASM artifacts in `dist` directory. wasm/: dynamic-assets/: # Assets used by the WASM application. diff --git a/build/build/src/ide/web.rs b/build/build/src/ide/web.rs index e0a790b2c220..8860c3fea605 100644 --- a/build/build/src/ide/web.rs +++ b/build/build/src/ide/web.rs @@ -7,6 +7,7 @@ use crate::project::gui::BuildInfo; use crate::project::wasm; use crate::project::ProcessWrapper; +use crate::project::ide::IsGuiArtifact; use anyhow::Context; use futures_util::future::try_join; use futures_util::future::try_join4; @@ -364,7 +365,7 @@ impl IdeDesktop { err))] pub async fn dist( &self, - gui: &crate::project::gui::Artifact, + gui: &impl IsGuiArtifact, project_manager: &crate::project::backend::Artifact, output_path: impl AsRef, target_os: OS, @@ -382,7 +383,7 @@ impl IdeDesktop { let pm_bundle = ProjectManagerInfo::new(project_manager)?; let client_build = self .npm()? - .set_env(env::ENSO_BUILD_GUI, gui.as_path())? + .set_env(env::ENSO_BUILD_GUI, gui.as_ref())? .set_env(env::ENSO_BUILD_IDE, output_path.as_ref())? .try_applying(&pm_bundle)? .workspace(Workspaces::Enso) @@ -413,7 +414,7 @@ impl IdeDesktop { self.npm()? .try_applying(&icons)? // .env("DEBUG", "electron-builder") - .set_env(env::ENSO_BUILD_GUI, gui.as_path())? + .set_env(env::ENSO_BUILD_GUI, gui.as_ref())? .set_env(env::ENSO_BUILD_IDE, output_path.as_ref())? .set_env(env::ENSO_BUILD_PROJECT_MANAGER, project_manager.as_ref())? .set_env_opt(env::PYTHON_PATH, python_path.as_ref())? diff --git a/build/build/src/project.rs b/build/build/src/project.rs index 608fe40eb092..9551eeba4a2b 100644 --- a/build/build/src/project.rs +++ b/build/build/src/project.rs @@ -27,7 +27,9 @@ use octocrab::models::repos::Asset; pub mod backend; pub mod engine; pub mod gui; +pub mod gui2; pub mod ide; +pub mod ide2; pub mod project_manager; pub mod runtime; pub mod wasm; diff --git a/build/build/src/project/gui2.rs b/build/build/src/project/gui2.rs new file mode 100644 index 000000000000..0ec4e35a0f0c --- /dev/null +++ b/build/build/src/project/gui2.rs @@ -0,0 +1,161 @@ +use crate::prelude::*; +use ide_ci::ok_ready_boxed; + +use crate::paths::generated::RepoRootAppGui2; +use crate::paths::generated::RepoRootAppGui2Dist; +use crate::paths::generated::RepoRootDistGui2Assets; +use crate::project::Context; +use crate::project::IsArtifact; +use crate::project::IsTarget; + +use crate::source::BuildTargetJob; +use crate::source::WithDestination; +use ide_ci::program::EMPTY_ARGS; +use ide_ci::programs::node::NpmCommand; +use ide_ci::programs::Npm; + + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::AsRefStr)] +#[strum(serialize_all = "kebab-case")] +pub enum Scripts { + Dev, + Build, + Preview, + #[strum(serialize = "test:unit")] + TestUnit, + #[strum(serialize = "test:e2e")] + TestE2e, + BuildOnly, + TypeCheck, + Lint, + Format, + BuildRustFfi, +} + +#[async_trait] +pub trait LocateGui2 { + fn package_dir(&self) -> &Path; + + fn npm(&self) -> Result { + Npm.cmd().map(|c| c.with_current_dir(self.package_dir())) + } + + fn run_cmd(&self, adjust_cmd: impl FnOnce(&mut NpmCommand)) -> BoxFuture { + self.npm() + .and_then_async(|mut cmd| { + adjust_cmd(&mut cmd); + cmd.run_ok() + }) + .boxed() + } + + fn install(&self) -> BoxFuture { + self.run_cmd(|c| { + c.install(); + }) + } + + fn run_script(&self, script: Scripts) -> BoxFuture { + self.run_cmd(|c| { + c.run(script.as_ref(), EMPTY_ARGS); + }) + } +} + + +#[async_trait] +impl LocateGui2 for RepoRootAppGui2 { + fn package_dir(&self) -> &Path { + self.as_ref() + } +} + +impl LocateGui2 for crate::paths::generated::RepoRoot { + fn package_dir(&self) -> &Path { + self.app.gui_2.as_ref() + } +} + +impl LocateGui2 for Path { + fn package_dir(&self) -> &Path { + self + } +} + +pub async fn lint(path: &impl LocateGui2) -> Result { + path.run_script(Scripts::Lint).await?; + path.run_script(Scripts::TypeCheck).await +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Deref)] +pub struct Artifact(pub RepoRootAppGui2Dist); + +impl AsRef for Artifact { + fn as_ref(&self) -> &Path { + self.0.as_path() + } +} + +impl IsArtifact for Artifact {} + +impl Artifact { + pub fn new(path: impl AsRef) -> Self { + Artifact(RepoRootAppGui2Dist::new_root(path.as_ref())) + } +} + + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Gui2; + +impl IsTarget for Gui2 { + type BuildInput = (); + type Artifact = Artifact; + + fn artifact_name(&self) -> String { + "gui2".to_owned() + } + + fn adapt_artifact(self, path: impl AsRef) -> BoxFuture<'static, Result> { + ok_ready_boxed(Artifact::new(path)) + } + + fn build_internal( + &self, + context: Context, + job: BuildTargetJob, + ) -> BoxFuture<'static, Result> { + let WithDestination { inner: _, destination } = job; + async move { + let gui2 = &context.repo_root.app.gui_2; + gui2.install().await?; + gui2.run_script(Scripts::Build).await?; + ide_ci::fs::mirror_directory( + &gui2.dist, + &destination.join(RepoRootDistGui2Assets::segment_name()), + ) + .await?; + Ok(Artifact::new(destination)) + } + .boxed() + } +} + + +#[cfg(test)] +mod tests { + use super::*; + use crate::paths::generated::RepoRootAppGui2; + use crate::repo::deduce_repository_path; + + #[tokio::test] + async fn foo() -> Result { + setup_logging()?; + let repo_root = deduce_repository_path()?; + info!("repo_root = {}", repo_root.display()); + let gui2 = RepoRootAppGui2::new(&repo_root); + gui2.install().await?; + gui2.run_script(Scripts::Build).await?; + Ok(()) + } +} diff --git a/build/build/src/project/ide.rs b/build/build/src/project/ide.rs index 18f30c55390b..10c34281a8ea 100644 --- a/build/build/src/project/ide.rs +++ b/build/build/src/project/ide.rs @@ -4,14 +4,15 @@ use crate::project::gui::ide_desktop_from_context; use crate::project::gui::GuiBuildWithWatchedWasm; use crate::project::Context; use crate::project::Gui; +use crate::project::IsArtifact; use crate::source::WatchTargetJob; +use crate::paths::generated::RepoRoot; use ide_ci::actions::artifacts::upload_compressed_directory; use ide_ci::actions::artifacts::upload_single_file; use ide_ci::actions::workflow::is_in_env; - #[derive(Clone, Debug)] pub struct Artifact { /// Directory with unpacked client distribution. @@ -25,7 +26,7 @@ pub struct Artifact { } impl Artifact { - fn new( + pub fn new( target_os: OS, target_arch: Arch, version: &Version, @@ -89,15 +90,34 @@ impl Artifact { } } +pub trait IsGuiArtifact: IsArtifact + Debug { + fn symlink_ensogl_dist(&self, repo_root: &RepoRoot) -> Result; +} + +impl IsGuiArtifact for crate::project::gui::Artifact { + fn symlink_ensogl_dist(&self, repo_root: &RepoRoot) -> Result { + self.symlink_ensogl_dist(&repo_root.target.ensogl_pack.linked_dist) + } +} +impl IsGuiArtifact for crate::project::gui2::Artifact { + fn symlink_ensogl_dist(&self, repo_root: &RepoRoot) -> Result { + let old_gui = crate::project::gui::Artifact::new(&repo_root.dist.gui); + // Old gui dir must exist + ensure!(old_gui.exists(), "Old GUI distribution does not exist at {}.", old_gui.display()); + IsGuiArtifact::symlink_ensogl_dist(&old_gui, repo_root) + } +} + + #[derive(derivative::Derivative)] #[derivative(Debug)] -pub struct BuildInput { +pub struct BuildInput { #[derivative(Debug(format_with = "std::fmt::Display::fmt"))] pub version: Version, #[derivative(Debug = "ignore")] pub project_manager: BoxFuture<'static, Result>, #[derivative(Debug = "ignore")] - pub gui: BoxFuture<'static, Result>, + pub gui: BoxFuture<'static, Result>, pub electron_target: Option, } @@ -114,20 +134,20 @@ impl Default for Ide { } impl Ide { - pub fn build( + pub fn build( &self, context: &Context, - input: BuildInput, + input: BuildInput, output_path: impl AsRef + Send + Sync + 'static, ) -> BoxFuture<'static, Result> { let BuildInput { version, project_manager, gui, electron_target } = input; - let linked_dist = context.repo_root.target.ensogl_pack.linked_dist.clone(); + let repo_root = context.repo_root.clone(); let ide_desktop = ide_desktop_from_context(context); let target_os = self.target_os; let target_arch = self.target_arch; async move { let (gui, project_manager) = try_join!(gui, project_manager)?; - gui.symlink_ensogl_dist(&linked_dist)?; + gui.symlink_ensogl_dist(&repo_root)?; ide_desktop .dist(&gui, &project_manager, &output_path, target_os, electron_target) .await?; diff --git a/build/build/src/project/ide2.rs b/build/build/src/project/ide2.rs new file mode 100644 index 000000000000..441248e0c85a --- /dev/null +++ b/build/build/src/project/ide2.rs @@ -0,0 +1,4 @@ +#[allow(unused_imports)] +use crate::prelude::*; + +pub use crate::project::ide::Artifact; diff --git a/build/cli/src/arg.rs b/build/cli/src/arg.rs index 8f9079678c4f..4524c8805aa3 100644 --- a/build/cli/src/arg.rs +++ b/build/cli/src/arg.rs @@ -20,7 +20,9 @@ pub mod backend; pub mod engine; pub mod git_clean; pub mod gui; +pub mod gui2; pub mod ide; +pub mod ide2; pub mod java_gen; pub mod project_manager; pub mod release; @@ -111,8 +113,10 @@ macro_rules! source_args_hlp { pub enum Target { /// Build/Test the Rust part of the GUI. Wasm(wasm::Target), - /// Build/Run GUI that consists of WASM and JS parts. This is what we deploy to cloud. + /// Build/Run the legacy Rust-based GUI that consists of WASM and JS parts. Gui(gui::Target), + /// Build/Run the new, Vue-based GUI. + Gui2(gui2::Target), /// Enso Engine Runtime. Runtime(runtime::Target), // /// Project Manager package (just the binary, no Engine) @@ -121,8 +125,10 @@ pub enum Target { // Engine(engine::Target), /// Build/Get Project Manager bundle (includes Enso Engine with GraalVM Runtime). Backend(backend::Target), - /// Build/Run/Test IDE bundle (includes GUI and Project Manager). + /// Build/Run/Test IDE bundle (includes Rust-based GUI and Project Manager). Ide(ide::Target), + /// Build/Run/Test IDE bundle (includes Vue-based GUI and Project Manager). + Ide2(ide2::Target), /// Clean the repository. Keeps the IntelliJ's .idea directory intact. WARNING: This removes /// files that are not under version control in the repository subtree. GitClean(git_clean::Options), diff --git a/build/cli/src/arg/gui2.rs b/build/cli/src/arg/gui2.rs new file mode 100644 index 000000000000..feb6b972e846 --- /dev/null +++ b/build/cli/src/arg/gui2.rs @@ -0,0 +1,30 @@ +use enso_build::prelude::*; + +use crate::arg::BuildJob; +use crate::arg::Source; +use crate::source_args_hlp; + +use clap::Args; +use clap::Subcommand; +use enso_build::project::gui2::Gui2; + + +source_args_hlp!(Gui2, "gui2", BuildInput); + +#[derive(Args, Clone, Copy, Debug, PartialEq)] +pub struct BuildInput {} + +#[derive(Subcommand, Clone, Debug, PartialEq)] +pub enum Command { + /// Builds the GUI from the local sources. + Build(BuildJob), + /// Gets the GUI, either by compiling it from scratch or downloading from an external source. + Get(Source), +} + +#[derive(Args, Clone, Debug)] +pub struct Target { + /// Command for GUI package. + #[clap(subcommand)] + pub command: Command, +} diff --git a/build/cli/src/arg/ide2.rs b/build/cli/src/arg/ide2.rs new file mode 100644 index 000000000000..c6a622acf603 --- /dev/null +++ b/build/cli/src/arg/ide2.rs @@ -0,0 +1,78 @@ +use crate::prelude::*; + +use crate::arg::Source; +use crate::source_args_hlp; + +use clap::Args; +use clap::Subcommand; +use enso_build::project::gui2::Gui2; + + + +source_args_hlp!(Target, "ide2", BuildInput); + +#[derive(Args, Clone, Debug, PartialEq)] +pub struct BuildInput { + #[clap(flatten)] + pub(crate) ide: crate::arg::ide::BuildInput, + #[clap(flatten)] + pub gui: Source, +} + +#[derive(Subcommand, Clone, Debug)] +pub enum Command { + /// Builds both Project Manager and GUI, puts them together into a single, client Electron + /// application. + Build { + #[clap(flatten)] + params: BuildInput, + }, + // Upload { + // #[clap(flatten)] + // params: BuildInput, + // #[clap(long, env = *enso_build::env::ENSO_RELEASE_ID)] + // release_id: ReleaseId, + // }, + // /// Like `Build` but automatically starts the IDE. + // Start { + // #[clap(flatten)] + // params: BuildInput, + // /// Additional option to be passed to Enso IDE. Can be used multiple times to pass many + // /// arguments. + // #[clap(long, allow_hyphen_values = true, enso_env())] + // ide_option: Vec, + // }, + // Watch { + // #[clap(flatten)] + // gui: WatchJob, + // #[clap(flatten)] + // project_manager: Source, + // #[clap(long, allow_hyphen_values = true, enso_env())] + // ide_option: Vec, + // }, + // /// Runs integration tests. This involves building and spawning Project Manager, unless + // /// requested otherwise. + // IntegrationTest { + // /// If set, the project manager won't be spawned. + // #[clap(long)] + // external_backend: bool, + // #[clap(flatten)] + // project_manager: Source, + // /// Run WASM tests in the headless mode + // #[clap(long, parse(try_from_str), default_value_t = true)] + // headless: bool, + // /// Custom timeout for wasm-bindgen test runner. Supports formats like "300secs" or + // "5min". #[clap(long, default_value_t = + // DEFAULT_INTEGRATION_TESTS_WASM_TIMEOUT.into())] wasm_timeout: + // humantime::Duration, /// Additional options to be appended to the wasm-pack + // invocation. Note that wasm-pack will /// further redirect any unrecognized option + // to the underlying cargo call. #[clap(last = true)] + // wasm_pack_options: Vec, + // }, +} + +#[derive(Args, Clone, Debug)] +pub struct Target { + #[clap(subcommand)] + pub command: Command, +} diff --git a/build/cli/src/lib.rs b/build/cli/src/lib.rs index b107fadfbc0b..0682e703b5fa 100644 --- a/build/cli/src/lib.rs +++ b/build/cli/src/lib.rs @@ -54,8 +54,10 @@ use enso_build::project::backend; use enso_build::project::backend::Backend; use enso_build::project::gui; use enso_build::project::gui::Gui; +use enso_build::project::gui2::Gui2; use enso_build::project::ide; use enso_build::project::ide::Ide; +use enso_build::project::ide2; use enso_build::project::runtime; use enso_build::project::runtime::Runtime; use enso_build::project::wasm; @@ -340,6 +342,13 @@ impl Processor { } } + pub fn handle_gui2(&self, gui: arg::gui2::Target) -> BoxFuture<'static, Result> { + match gui.command { + arg::gui2::Command::Build(job) => self.build(job), + arg::gui2::Command::Get(source) => self.get(source).void_ok().boxed(), + } + } + pub fn handle_runtime(&self, gui: arg::runtime::Target) -> BoxFuture<'static, Result> { // todo!() match gui.command { @@ -541,6 +550,12 @@ impl Processor { } } + pub fn handle_ide2(&self, ide: arg::ide2::Target) -> BoxFuture<'static, Result> { + match ide.command { + arg::ide2::Command::Build { params } => self.build_ide2(params).void_ok().boxed(), + } + } + /// Spawns a Project Manager. pub fn spawn_project_manager( &self, @@ -584,6 +599,46 @@ impl Processor { .boxed() } + pub fn build_ide2( + &self, + params: arg::ide2::BuildInput, + ) -> BoxFuture<'static, Result> { + let arg::ide::BuildInput { gui, project_manager, output_path, electron_target } = + params.ide; + + let build_old_gui = self.get(gui); + let build_new_gui = self.get(params.gui); + let gui = async move { + let (old_gui, new_gui) = futures::future::join(build_old_gui, build_new_gui).await; + // We don't care about "official" artifacts of the old GUI, we just need it built. + // Actually we are interested only in the linked app bundle that is generated by the + // `ensogl-pack`. + let _ = old_gui?; + // We pass the new GUI artifacts to the IDE build. + new_gui + } + .boxed(); + + + let input = ide::BuildInput { + gui, + project_manager: self.get(project_manager), + version: self.triple.versions.version.clone(), + electron_target, + }; + + let target = Ide { target_os: self.triple.os, target_arch: self.triple.arch }; + let build_job = target.build(&self.context, input, output_path); + async move { + let artifacts = build_job.await?; + if is_in_env() { + artifacts.upload_as_ci_artifact().await?; + } + Ok(artifacts) + } + .boxed() + } + pub fn target(&self) -> Result { Target::prepare_target(self) } @@ -649,6 +704,19 @@ impl Resolvable for Gui { } } +impl Resolvable for Gui2 { + fn prepare_target(_context: &Processor) -> Result { + Ok(Gui2) + } + + fn resolve( + _ctx: &Processor, + _from: ::BuildInput, + ) -> BoxFuture<'static, Result<::BuildInput>> { + ok_ready_boxed(()) + } +} + impl Resolvable for Runtime { fn prepare_target(_context: &Processor) -> Result { Ok(Runtime {}) @@ -790,13 +858,14 @@ pub async fn main_internal(config: Option) -> Result match cli.target { Target::Wasm(wasm) => ctx.handle_wasm(wasm).await?, Target::Gui(gui) => ctx.handle_gui(gui).await?, + Target::Gui2(gui2) => ctx.handle_gui2(gui2).await?, Target::Runtime(runtime) => ctx.handle_runtime(runtime).await?, // Target::ProjectManager(project_manager) => // ctx.handle_project_manager(project_manager).await?, // Target::Engine(engine) => ctx.handle_engine(engine).await?, Target::Backend(backend) => ctx.handle_backend(backend).await?, Target::Ide(ide) => ctx.handle_ide(ide).await?, - // TODO: consider if out-of-source ./dist should be removed + Target::Ide2(ide2) => ctx.handle_ide2(ide2).await?, Target::GitClean(options) => { let crate::arg::git_clean::Options { dry_run, cache, build_script } = options; let mut exclusions = vec![".idea"]; @@ -835,6 +904,8 @@ pub async fn main_internal(config: Option) -> Result .run_ok() .await?; + project::gui2::lint(&ctx.repo_root).await?; + ensogl_pack::build_ts_sources_only().await?; prettier::check(&ctx.repo_root).await?; let js_modules_root = ctx.repo_root.join("app/ide-desktop"); From d8e1607ec62a6920d75107bef8ee96a893259dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20W=2E=20Urba=C5=84czyk?= Date: Mon, 18 Sep 2023 13:41:19 +0200 Subject: [PATCH 02/29] wip --- app/gui2/rust-ffi/Cargo.toml | 1 - .../lib/client/electron-builder-config.ts | 12 +- build/build/src/ide/web.rs | 2 +- build/build/src/project/gui2.rs | 119 ++++++++++++++++-- build/build/src/project/ide.rs | 3 +- build/build/src/project/ide2.rs | 5 + build/ci_utils/src/program/command.rs | 9 ++ build/cli/src/arg/gui2.rs | 5 + build/cli/src/lib.rs | 53 ++++---- .../src/display/render/passes/cache_shapes.rs | 1 + .../core/src/display/render/passes/screen.rs | 1 + lib/rust/ensogl/pack/src/lib.rs | 1 + 12 files changed, 160 insertions(+), 52 deletions(-) diff --git a/app/gui2/rust-ffi/Cargo.toml b/app/gui2/rust-ffi/Cargo.toml index 1f7e6b657a3c..ca376d7750e6 100644 --- a/app/gui2/rust-ffi/Cargo.toml +++ b/app/gui2/rust-ffi/Cargo.toml @@ -18,6 +18,5 @@ wasm-bindgen = { workspace = true } #lto = true #codegen-units = 1 #opt-level = "z" - [package.metadata.wasm-pack.profile.release] wasm-opt = ['-Os'] diff --git a/app/ide-desktop/lib/client/electron-builder-config.ts b/app/ide-desktop/lib/client/electron-builder-config.ts index 9b1b8f6111fd..32329130f387 100644 --- a/app/ide-desktop/lib/client/electron-builder-config.ts +++ b/app/ide-desktop/lib/client/electron-builder-config.ts @@ -95,21 +95,13 @@ export const args: Arguments = await yargs(process.argv.slice(2)) export async function createElectronBuilderConfig( passedArgs: Arguments ): Promise { - // Check if source GUI directory has assets subdirectory. - // const sourceGuiAssets = path.join(passedArgs.guiDist, 'assets') - // const doesGuiHaveAssets = (await fs.stat(sourceGuiAssets)).isDirectory() - const doesGuiHaveAssets = true - // The Rust-based GUI ships assets directory which is then copied to the IDE. The vue-based GUI goes without it, - // so to make the build work for both versions we place the files in the assets directory by hand. - const guiDestination = doesGuiHaveAssets ? '.' : './assets' - return { appId: 'org.enso', productName: common.PRODUCT_NAME, extraMetadata: { version: BUILD_INFO.version, }, - copyright: `Copyright © ${new Date(). getFullYear()} $\{author}.`, + copyright: `Copyright © ${new Date().getFullYear()} $\{author}.`, artifactName: 'enso-${os}-${version}.${ext}', /** Definitions of URL {@link electronBuilder.Protocol} schemes used by the IDE. * @@ -171,7 +163,7 @@ export async function createElectronBuilderConfig( }, files: [ '!**/node_modules/**/*', - { from: `${passedArgs.guiDist}/`, to: guiDestination }, + { from: `${passedArgs.guiDist}/`, to: '.' }, { from: `${passedArgs.ideDist}/client`, to: '.' }, ], extraResources: [ diff --git a/build/build/src/ide/web.rs b/build/build/src/ide/web.rs index 8860c3fea605..a477954a7e37 100644 --- a/build/build/src/ide/web.rs +++ b/build/build/src/ide/web.rs @@ -4,10 +4,10 @@ use crate::ide::web::env::CSC_KEY_PASSWORD; use crate::paths::generated; use crate::paths::generated::RepoRootTargetEnsoglPackLinkedDist; use crate::project::gui::BuildInfo; +use crate::project::ide::IsGuiArtifact; use crate::project::wasm; use crate::project::ProcessWrapper; -use crate::project::ide::IsGuiArtifact; use anyhow::Context; use futures_util::future::try_join; use futures_util::future::try_join4; diff --git a/build/build/src/project/gui2.rs b/build/build/src/project/gui2.rs index 0ec4e35a0f0c..074747698f1e 100644 --- a/build/build/src/project/gui2.rs +++ b/build/build/src/project/gui2.rs @@ -1,5 +1,8 @@ +//! Build logic for the "new GUI" (gui2) project. +//! +//! The new GUI is Vue.js-based and located under `app/gui2`. + use crate::prelude::*; -use ide_ci::ok_ready_boxed; use crate::paths::generated::RepoRootAppGui2; use crate::paths::generated::RepoRootAppGui2Dist; @@ -7,14 +10,26 @@ use crate::paths::generated::RepoRootDistGui2Assets; use crate::project::Context; use crate::project::IsArtifact; use crate::project::IsTarget; - use crate::source::BuildTargetJob; use crate::source::WithDestination; + +use ide_ci::ok_ready_boxed; use ide_ci::program::EMPTY_ARGS; use ide_ci::programs::node::NpmCommand; +use std::process::Stdio; + + + use ide_ci::programs::Npm; +use ide_ci::programs::Npx; + +// =============== +// === Scripts === +// =============== + +/// The scripts defined in `package.json`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::AsRefStr)] #[strum(serialize_all = "kebab-case")] pub enum Scripts { @@ -32,14 +47,30 @@ pub enum Scripts { BuildRustFfi, } + + +// ================ +// === Location === +// ================ + +/// Convenience trait for types that allow us locating the new GUI sources. +/// +/// Provides helper methods for calling gui2-related commands. #[async_trait] -pub trait LocateGui2 { +pub trait Locate { fn package_dir(&self) -> &Path; + /// Prepare command that will run `npm` in the new GUI's directory. fn npm(&self) -> Result { - Npm.cmd().map(|c| c.with_current_dir(self.package_dir())) + Ok(self.adjust_cmd(Npm.cmd()?)) } + /// Set common data for commands run in the new GUI's directory. + fn adjust_cmd(&self, cmd: Cmd) -> Cmd { + cmd.with_current_dir(self.package_dir()).with_stdin(Stdio::null()) + } + + /// Run an NPM command in the new GUI's directory. fn run_cmd(&self, adjust_cmd: impl FnOnce(&mut NpmCommand)) -> BoxFuture { self.npm() .and_then_async(|mut cmd| { @@ -49,44 +80,93 @@ pub trait LocateGui2 { .boxed() } + /// Run `npm install` in the new GUI's directory. fn install(&self) -> BoxFuture { self.run_cmd(|c| { c.install(); }) } + /// Run `npm run