From 5a32cf420f517ce57957b73aa3aa32bf0f1aedb1 Mon Sep 17 00:00:00 2001 From: Ira Hopkinson Date: Tue, 28 Feb 2023 04:45:17 +1300 Subject: [PATCH] Run the dotnet Data Provider from electron (#36) - console messages are passed through to the electron console - add GHA dotnet build - also `npx update-browserslist-db@latest` - note: in dev, close the app (before ctrl + c) so that the dotnet process is killed --- .github/workflows/publish.yml | 20 +++++- .github/workflows/test.yml | 14 +++- README.md | 13 +++- c-sharp/ParanextDataProvider.csproj | 1 + package-lock.json | 12 ++-- package.json | 29 ++++++-- src/main/main.ts | 43 +++++++----- .../services/dotnet-data-provider.service.ts | 70 +++++++++++++++++++ src/renderer/App.tsx | 10 ++- 9 files changed, 178 insertions(+), 34 deletions(-) create mode 100644 src/main/services/dotnet-data-provider.service.ts diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5e8183c737..2613cab367 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,19 +13,25 @@ on: jobs: publish: - name: Build on node ${{ matrix.node_version }} and ${{ matrix.os }} + name: Publish on ${{ matrix.os }}, .Net ${{ matrix.dotnet_version }}, and node ${{ matrix.node_version }} runs-on: ${{ matrix.os }} strategy: matrix: - node_version: [16.x] os: [macos-latest, ubuntu-latest] + dotnet_version: [7.0.x] + node_version: [16.x] steps: - name: Checkout git repo uses: actions/checkout@v3 + - name: Install .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{matrix.dotnet_version}} + - name: Install Node and NPM uses: actions/setup-node@v3 with: @@ -37,6 +43,16 @@ jobs: npm install npm run build + - name: dotnet build - MacOS and Windows + if: ${{ matrix.os == 'macos-latest' }} + run: | + npm run build:data-release:windows + npm run build:data-release:macos + + - name: dotnet build - Linux + if: ${{ matrix.os == 'ubuntu-latest' }} + run: npm run build:data-release:linux + - name: Publish releases - Windows and MacOS # If the branch is labeled as a release version (e.g. "release/v1.2.3"), if: ${{ matrix.os == 'macos-latest' && startsWith(github.ref, 'refs/heads/release/v') && contains(github.ref, '.') }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 08b67d870e..dfb1a1b733 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,26 +16,38 @@ on: jobs: test: + name: Test on ${{ matrix.os }}, .Net ${{ matrix.dotnet_version }}, and node ${{ matrix.node_version }} + runs-on: ${{ matrix.os }} strategy: matrix: os: [macos-latest, windows-latest, ubuntu-latest] + dotnet_version: [7.0.x] + node_version: [16.x] steps: - name: Check out Git repository uses: actions/checkout@v3 + - name: Install .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{matrix.dotnet_version}} + - name: Install Node.js and NPM uses: actions/setup-node@v3 with: - node-version: 16 + node-version: ${{ matrix.node_version }} cache: npm - name: npm install run: | npm install + - name: dotnet build + run: npm run build:data-release + - name: npm test env: # no hardlinks so dependencies are copied diff --git a/README.md b/README.md index 98932a696e..762600a797 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,17 @@ Set up pre-requisites for building: Add the system libraries needed for Electron, [Build Instructions (Linux)](https://www.electronjs.org/docs/latest/development/build-instructions-linux). +### All Platforms Development Pre-requisites + +Install `dotnet` [.NET 7 SDK from here](https://learn.microsoft.com/en-us/dotnet/core/install/). + +To check if `dotnet` is installed run (ensure you have a v7 SDK): + +```bash +dotnet --version +dotnet --list-sdks +``` + ### Cloning and installing dependencies (all platforms) Clone the repo and install dependencies: @@ -59,7 +70,7 @@ Start the app in the `dev` environment: npm start ``` -After you run `npm start`, you can edit the Electron and frontend files, and they will hot reload. To edit C# files, you must stop the `npm start` process (or only close Paranext), run `npm run build:c-sharp`, and restart `npm start` (or if you only closed Paranext, make a trivial edit to `src/main/main.ts`, and save it to launch Paranext again). +After you run `npm start`, you can edit the Electron and frontend files, and they will hot reload. To edit C# files, you must stop the `npm start` process (or only close Paranext), run `npm run build:data`, and restart `npm start` (or if you only closed Paranext, make a trivial edit to `src/main/main.ts`, and save it to launch Paranext again). ## Packaging for Production diff --git a/c-sharp/ParanextDataProvider.csproj b/c-sharp/ParanextDataProvider.csproj index 7386c62ff3..5191fc595c 100644 --- a/c-sharp/ParanextDataProvider.csproj +++ b/c-sharp/ParanextDataProvider.csproj @@ -6,6 +6,7 @@ enable enable Paranext.DataProvider + true diff --git a/package-lock.json b/package-lock.json index ceca18ad68..ab03f66288 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6023,9 +6023,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001449", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001449.tgz", - "integrity": "sha512-CPB+UL9XMT/Av+pJxCKGhdx+yg1hzplvFJQlJ2n68PyQGMz9L/E2zCyLdOL8uasbouTUgnPl+y0tccI/se+BEw==", + "version": "1.0.30001451", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz", + "integrity": "sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w==", "dev": true, "funding": [ { @@ -22558,9 +22558,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001449", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001449.tgz", - "integrity": "sha512-CPB+UL9XMT/Av+pJxCKGhdx+yg1hzplvFJQlJ2n68PyQGMz9L/E2zCyLdOL8uasbouTUgnPl+y0tccI/se+BEw==", + "version": "1.0.30001451", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001451.tgz", + "integrity": "sha512-XY7UbUpGRatZzoRft//5xOa69/1iGJRBlrieH6QYrkKLIFn3m7OVEJ81dSrKoy2BnKsdbX5cLrOispZNYo9v2w==", "dev": true }, "chalk": { diff --git a/package.json b/package.json index ead9753ce9..d02bd97f66 100644 --- a/package.json +++ b/package.json @@ -46,9 +46,9 @@ "build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts", "build:data": "dotnet build c-sharp/ParanextDataProvider.sln", "build:data-release": "run-script-os", - "build:data-release:windows": "dotnet publish c-sharp/ParanextDataProvider.csproj -p:PublishProfile=FolderProfile", - "build:data-release:linux": "dotnet publish c-sharp/ParanextDataProvider.csproj -p:PublishProfile=FolderProfile -r linux-x64 -o ./c-sharp/bin/Release/net7.0/publish/linux-x64/", - "build:data-release:macos": "dotnet publish c-sharp/ParanextDataProvider.csproj -p:PublishProfile=FolderProfile -r osx-x64 -o ./c-sharp/bin/Release/net7.0/publish/osx-x64/ && dotnet publish c-sharp/ParanextDataProvider.csproj -p:PublishProfile=FolderProfile -r osx-arm64 -o ./c-sharp/bin/Release/net7.0/publish/osx-arm64/", + "build:data-release:windows": "dotnet publish c-sharp/ParanextDataProvider.csproj -r win-x64 -o ./c-sharp/bin/Release/net7.0/publish/win-x64/", + "build:data-release:linux": "dotnet publish c-sharp/ParanextDataProvider.csproj -r linux-x64 -o ./c-sharp/bin/Release/net7.0/publish/linux-x64/", + "build:data-release:macos": "dotnet publish c-sharp/ParanextDataProvider.csproj -r osx-x64 -o ./c-sharp/bin/Release/net7.0/publish/osx-x64/ && dotnet publish c-sharp/ParanextDataProvider.csproj -p:PublishProfile=FolderProfile -r osx-arm64 -o ./c-sharp/bin/Release/net7.0/publish/osx-arm64/", "postinstall": "ts-node .erb/scripts/check-native-dep.js && electron-builder install-app-deps && cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.dev.dll.ts", "lint": "cross-env NODE_ENV=development eslint . --ext .js,.jsx,.ts,.tsx", "package": "ts-node ./.erb/scripts/clean.js dist && npm run build && npm run build:data-release && electron-builder build --publish never", @@ -59,6 +59,7 @@ "start:main": "cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only -r tsconfig-paths/register .", "start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts", "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts", + "start:data": "dotnet run --project c-sharp/ParanextDataProvider.csproj", "test": "jest" }, "lint-staged": { @@ -178,6 +179,12 @@ "x64" ] }, + "extraResources": [ + { + "from": "./c-sharp/bin/Release/net7.0/publish/osx-${arch}/", + "to": "./dotnet/" + } + ], "type": "distribution", "hardenedRuntime": true, "entitlements": "assets/entitlements.mac.plist", @@ -203,12 +210,25 @@ "nsis", "nsis-web", "portable" + ], + "extraResources": [ + { + "from": "./c-sharp/bin/Release/net7.0/publish/win-x64/", + "to": "./dotnet/" + } ] }, "linux": { "target": [ "AppImage" ], + "category": "Development", + "extraResources": [ + { + "from": "./c-sharp/bin/Release/net7.0/publish/linux-x64/", + "to": "./dotnet/" + } + ], "extraFiles": [ { "from": "/usr/lib/x86_64-linux-gnu/libnss3.so", @@ -466,8 +486,7 @@ "from": "/usr/lib/x86_64-linux-gnu/libgio-2.0.so.0", "to": "usr/lib/libgio-2.0.so.0" } - ], - "category": "Development" + ] }, "directories": { "app": "release/app", diff --git a/src/main/main.ts b/src/main/main.ts index 4cf5c4764b..72eb8f42b7 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -12,10 +12,11 @@ import path from 'path'; import { app, BrowserWindow, shell, ipcMain } from 'electron'; import { autoUpdater } from 'electron-updater'; import log from 'electron-log'; +import windowStateKeeper from 'electron-window-state'; +import dotnetDataProvider from '@main/services/dotnet-data-provider.service'; import * as NetworkService from '@shared/services/NetworkService'; import papi from '@shared/services/papi'; import { CommandHandler } from '@shared/util/PapiUtil'; -import windowStateKeeper from 'electron-window-state'; import MenuBuilder from './menu'; import { resolveHtmlPath } from './util'; @@ -134,10 +135,17 @@ app.on('window-all-closed', () => { // Respect the OSX convention of having the application in memory even // after all windows have been closed if (process.platform !== 'darwin') { + // TODO: cleanly stop the provider (close the ws or send command) - IJH 2022-02-23 + dotnetDataProvider.kill(); app.quit(); } }); +app.on('will-quit', () => { + // TODO: cleanly stop the provider (close the ws or send command) - IJH 2022-02-23 + dotnetDataProvider.kill(); +}); + // #endregion // #region IPC HANDLING SETUP @@ -190,20 +198,23 @@ const commandHandlers: { [commandName: string]: CommandHandler } = { }, }; -NetworkService.initialize() - .then(() => { - // Set up test handlers - Object.entries(ipcHandlers).forEach(([ipcHandle, handler]) => { - NetworkService.registerRequestHandler( - ipcHandle, - async (...args: unknown[]) => - handler({} as Electron.IpcMainInvokeEvent, ...args), - ); - }); - Object.entries(commandHandlers).forEach(([commandName, handler]) => { - papi.commands.registerCommand(commandName, handler); - }); - }) - .catch((e) => console.error(e)); +(async () => { + await NetworkService.initialize(); + // Set up test handlers + Object.entries(ipcHandlers).forEach(([ipcHandle, handler]) => { + NetworkService.registerRequestHandler( + ipcHandle, + async (...args: unknown[]) => + handler({} as Electron.IpcMainInvokeEvent, ...args), + ); + }); + Object.entries(commandHandlers).forEach(([commandName, handler]) => { + papi.commands.registerCommand(commandName, handler); + }); + + // Start the dotnet data provider early so its ready when needed once the + // WebSocket is up. + dotnetDataProvider.start(); +})().catch(console.error); // #endregion diff --git a/src/main/services/dotnet-data-provider.service.ts b/src/main/services/dotnet-data-provider.service.ts new file mode 100644 index 0000000000..1b866d9684 --- /dev/null +++ b/src/main/services/dotnet-data-provider.service.ts @@ -0,0 +1,70 @@ +import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; +import path from 'path'; + +let dotnet: ChildProcessWithoutNullStreams | undefined; + +function killDotnetDataProvider() { + if (!dotnet) return; + + if (dotnet.kill()) { + console.log('[dotnet data provider] was killed'); + } else { + console.error( + '[dotnet data provider] was not stopped! Investigate other .kill() options', + ); + } + dotnet = undefined; +} + +/** + * Starts the Dotnet Data Provider if it isn't already running. + */ +function startDotnetDataProvider() { + if (dotnet) return; + + // default values for development + let command = process.platform.includes('win') ? 'npm.cmd' : 'npm'; + let args: string[] = ['run', 'start:data']; + + if (process.env.NODE_ENV === 'production') { + if (process.platform === 'win32') { + command = path.join( + process.resourcesPath, + 'dotnet', + 'ParanextDataProvider.exe', + ); + args = []; + } else { + command = path.join( + process.resourcesPath, + 'dotnet', + 'ParanextDataProvider', + ); + args = []; + } + } + + dotnet = spawn(command, args); + + dotnet.stdout.on('data', (data) => { + console.log(`[dotnet data provider] stdout: ${data}`); + }); + + dotnet.stderr.on('data', (data) => { + console.error(`[dotnet data provider] stderr: ${data}`); + }); + + dotnet.on('close', (code, signal) => { + if (signal) { + console.log(`[dotnet data provider] terminated with signal ${signal}`); + } else { + console.log(`[dotnet data provider] exited with code ${code}`); + } + }); +} + +const dotnetDataProvider = { + start: startDotnetDataProvider, + kill: killDotnetDataProvider, +}; +export default dotnetDataProvider; diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index ef9113e579..c9572a35e1 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -68,8 +68,10 @@ const executeMany = async (fn: () => Promise) => { } }; +let addResult = 0; + const Hello = () => { - const [promiseReturn, setPromiseReturn] = useState(''); + const [promiseReturn, setPromiseReturn] = useState('Click a button.'); const [NODE_ENV] = usePromise( useCallback(() => getVar('NODE_ENV'), []), @@ -136,8 +138,10 @@ const Hello = () => {