From c30b7d74105901d1556f1d2265c7c877792518ec Mon Sep 17 00:00:00 2001 From: "Yuri (solarw) Turchenkov" Date: Mon, 5 Aug 2024 19:57:44 +0300 Subject: [PATCH] initial win support --- .github/workflows/release_win.yml | 67 +++++++++++++++++++++++++++ Makefile | 49 ++++++++++++++++++++ build-win-tenderly.js | 38 +++++++++++++++ build-win.js | 38 +++++++++++++++ electron/main.js | 6 +++ operate/ledger/__init__.py | 8 ++++ operate/services/deployment_runner.py | 32 +++++++++++-- 7 files changed, 234 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/release_win.yml create mode 100644 Makefile create mode 100644 build-win-tenderly.js create mode 100644 build-win.js diff --git a/.github/workflows/release_win.yml b/.github/workflows/release_win.yml new file mode 100644 index 000000000..9173bcd85 --- /dev/null +++ b/.github/workflows/release_win.yml @@ -0,0 +1,67 @@ +name: Release for Windows + +# This workflow is triggered on pushing a tag BE CAREFUL this application AUTO UPDATES !!! +# git tag vX.Y.Z +# git push origin tag vX.Y.Z + +on: [pull_request] + +jobs: + build-windows: + runs-on: windows-latest + #strategy: + # matrix: + # arch: [ x64, arm64 ] + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - uses: actions/setup-node@v4 + with: + node-version: lts/* + + - uses: msys2/setup-msys2@v2 + with: + update: true + install: >- + curl + git + make + + - name: Install and configure Poetry + uses: snok/install-poetry@v1 + with: + # version: '1.4.0' + virtualenvs-create: true + virtualenvs-in-project: false + virtualenvs-path: ~/my-custom-path + installer-parallel: true + #- name: Setup tmate session + # uses: mxschmitt/action-tmate@v3 + - name: Install dependencies + run: poetry install + + - name: install node deps + run: yarn install-deps + + - name: set env vars to prod.env + env: + NODE_ENV: production + DEV_RPC: https://rpc-gate.autonolas.tech/gnosis-rpc/ + IS_STAGING: ${{ github.ref != 'refs/heads/main' && 'true' || 'false' }} + FORK_URL: https://rpc-gate.autonolas.tech/gnosis-rpc/ + run: | + echo NODE_ENV=$NODE_ENV >> prod.env + echo DEV_RPC=$DEV_RPC >> prod.env + echo IS_STAGING=$IS_STAGING >> prod.env + echo FORK_URL=$FORK_URL >> prod.env + cat prod.env + - run: rm -rf /dist + - name: "Build, notarize, publish" + run: make build diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..826bc5219 --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ + +define setup_env + $(eval ENV_FILE := $(1).env) + @echo " - setup env $(ENV_FILE)" + $(eval include $(1).env) + $(eval export) +endef + + +./trader/: + pwd + git clone https://github.com/valory-xyz/trader.git + +./dist/aea_win.exe: ./trader/ + mkdir -p dist + cd trader && poetry install && poetry run pyinstaller --collect-data eth_account --collect-all aea --collect-all autonomy --collect-all operate --collect-all aea_ledger_ethereum --collect-all aea_ledger_cosmos --collect-all aea_ledger_ethereum_flashbots --hidden-import aea_ledger_ethereum --hidden-import aea_ledger_cosmos --hidden-import aea_ledger_ethereum_flashbots --hidden-import grpc --hidden-import openapi_core --collect-all google.protobuf --collect-all openapi_core --collect-all openapi_spec_validator --collect-all asn1crypto --hidden-import py_ecc --hidden-import pytz --onefile pyinstaller/trader_bin.py --name trader_win + cp -f trader/dist/trader_win.exe ./dist/aea_win.exe + pwd + + +./dist/tendermint_win.exe: ./operate + pwd + poetry install && poetry run pyinstaller operate/services/utils/tendermint.py --onefile --name tendermint_win + + +./dist/pearl_win.exe: ./dist/aea_win.exe ./dist/tendermint_win.exe + pwd + poetry install && poetry run pyinstaller --collect-data eth_account --collect-all aea --collect-all coincurve --collect-all autonomy --collect-all operate --collect-all aea_ledger_ethereum --collect-all aea_ledger_cosmos --collect-all aea_ledger_ethereum_flashbots --hidden-import aea_ledger_ethereum --hidden-import aea_ledger_cosmos --hidden-import aea_ledger_ethereum_flashbots operate/pearl.py --add-binary dist/aea_win.exe:. --add-binary dist/tendermint_win.exe:. --onefile --name pearl_win + + +.PHONY: build +build: ./dist/pearl_win.exe + $(call setup_env, prod) + echo ${DEV_RPC} + mkdir -p ./electron/bins + cp -f dist/pearl_win.exe ./electron/bins/pearl_win.exe + echo ${NODE_ENV} + NODE_ENV=${NODE_ENV} DEV_RPC=${DEV_RPC} FORK_URL=${FORK_URL} yarn build:frontend + node build-win.js + + +.PHONY: build-tenderly +build-tenderly: ./dist/pearl_win.exe + $(call setup_env, dev-tenderly) + echo ${DEV_RPC} + cp -f dist/pearl_win.exe ./electron/bins/pearl_win.exe + echo ${NODE_ENV} + NODE_ENV=${NODE_ENV} DEV_RPC=${DEV_RPC} FORK_URL=${FORK_URL} yarn build:frontend + node build-win-tenderly.js \ No newline at end of file diff --git a/build-win-tenderly.js b/build-win-tenderly.js new file mode 100644 index 000000000..68fbdbbf4 --- /dev/null +++ b/build-win-tenderly.js @@ -0,0 +1,38 @@ +/** + * This script is used to build the electron app **with notarization**. It is used for the final build and release process. + */ +require('dotenv').config(); +const build = require('electron-builder').build; + +const { publishOptions } = require('./electron/constants'); + +const main = async () => { + console.log('Building...'); + + /** @type import {CliOptions} from "electron-builder" */ + await build({ + publish: 'onTag', + config: { + appId: 'xyz.valory.olas-operate-app', + artifactName: '${productName}-${version}-${platform}-${arch}-tenderly.${ext}', + productName: 'Pearl', + files: ['electron/**/*', 'package.json'], + directories: { + output: 'dist', + }, + nsis: { + oneClick: false, + }, + extraResources: [ + { + from: 'electron/bins', + to: 'bins', + filter: ['**/*'], + }, + ], + + }, + }); +}; + +main().then((response) => { console.log('Build & Notarize complete'); }).catch((e) => console.error(e)); diff --git a/build-win.js b/build-win.js new file mode 100644 index 000000000..7bf8db3e2 --- /dev/null +++ b/build-win.js @@ -0,0 +1,38 @@ +/** + * This script is used to build the electron app **with notarization**. It is used for the final build and release process. + */ +require('dotenv').config(); +const build = require('electron-builder').build; + +const { publishOptions } = require('./electron/constants'); + +const main = async () => { + console.log('Building...'); + + /** @type import {CliOptions} from "electron-builder" */ + await build({ + publish: 'onTag', + config: { + appId: 'xyz.valory.olas-operate-app', + artifactName: '${productName}-${version}-${platform}-${arch}.${ext}', + productName: 'Pearl', + files: ['electron/**/*', 'package.json'], + directories: { + output: 'dist', + }, + nsis: { + oneClick: false, + }, + extraResources: [ + { + from: 'electron/bins', + to: 'bins', + filter: ['**/*'], + }, + ], + + }, + }); +}; + +main().then((response) => { console.log('Build & Notarize complete'); }).catch((e) => console.error(e)); diff --git a/electron/main.js b/electron/main.js index e2c66a668..e44ae3954 100644 --- a/electron/main.js +++ b/electron/main.js @@ -39,6 +39,9 @@ const binaryPaths = { arm64: 'bins/pearl_arm64', x64: 'bins/pearl_x64', }, + win32: { + x64: 'bins/pearl_win.exe', + }, }; let appConfig = { @@ -281,6 +284,7 @@ async function launchDaemon() { } catch (err) { logger.electron('Backend not running!'); } + logger.info("11111111 " + JSON.stringify(Env, null, " ")); const check = new Promise(function (resolve, _reject) { operateDaemon = spawn( @@ -324,6 +328,7 @@ async function launchDaemon() { } async function launchDaemonDev() { + const check = new Promise(function (resolve, _reject) { operateDaemon = spawn('poetry', [ 'run', @@ -388,6 +393,7 @@ async function launchNextAppDev() { 'yarn', ['dev:frontend', '--port', appConfig.ports.dev.next], { + shell: true, env: { ...process.env, NEXT_PUBLIC_BACKEND_PORT: appConfig.ports.dev.operate, diff --git a/operate/ledger/__init__.py b/operate/ledger/__init__.py index 078a93d1e..82807f71d 100644 --- a/operate/ledger/__init__.py +++ b/operate/ledger/__init__.py @@ -38,6 +38,14 @@ GOERLI_RPC = os.environ.get("DEV_RPC", "https://ethereum-goerli.publicnode.com") SOLANA_RPC = os.environ.get("DEV_RPC", "https://api.mainnet-beta.solana.com") + +print( + 11111111111111111111, + "DEV_RPC", + os.environ.get("DEV_RPC", "https://gnosis-rpc.publicnode.com"), + flush=True +) + PUBLIC_RPCS = { ChainType.ETHEREUM: ETHEREUM_PUBLIC_RPC, ChainType.GNOSIS: GNOSIS_PUBLIC_RPC, diff --git a/operate/services/deployment_runner.py b/operate/services/deployment_runner.py index 1bf81904e..810cfbce3 100644 --- a/operate/services/deployment_runner.py +++ b/operate/services/deployment_runner.py @@ -31,7 +31,6 @@ from pathlib import Path from typing import Any from venv import main as venv_cli - import psutil from aea.__version__ import __version__ as aea_version from autonomy.__version__ import __version__ as autonomy_version @@ -95,6 +94,7 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta): def _run_aea(self, *args: str, cwd: Path) -> Any: """Run aea command.""" + print(222222222222222, self._aea_bin, args, flush=True) return self._run_cmd(args=[self._aea_bin, *args], cwd=cwd) @staticmethod @@ -243,7 +243,7 @@ def _start_agent(self) -> None: stderr=subprocess.DEVNULL, env={**os.environ, **env}, creationflags=( - 0x00000008 if platform.system() == "Windows" else 0 + 0x00000200 if platform.system() == "Windows" else 0 ), # Detach process from the main process ) (working_dir / "agent.pid").write_text( @@ -263,7 +263,7 @@ def _start_tendermint(self) -> None: stderr=subprocess.DEVNULL, env={**os.environ, **env}, creationflags=( - 0x00000008 if platform.system() == "Windows" else 0 + 0x00000200 if platform.system() == "Windows" else 0 ), # Detach process from the main process ) (working_dir / "tendermint.pid").write_text( @@ -272,6 +272,23 @@ def _start_tendermint(self) -> None: ) +class PyInstallerHostDeploymentRunnerMac(PyInstallerHostDeploymentRunner): + pass + + +class PyInstallerHostDeploymentRunnerWindows(PyInstallerHostDeploymentRunner): + @property + def _aea_bin(self) -> str: + """Return aea_bin path.""" + abin = str(Path(sys._MEIPASS) / "aea_win.exe") # type: ignore # pylint: disable=protected-access + return abin + + @property + def _tendermint_bin(self) -> str: + """Return tendermint path.""" + return str(Path(sys._MEIPASS) / "tendermint_win.exe") # type: ignore # pylint: disable=protected-access + + class HostPythonHostDeploymentRunner(BaseDeploymentRunner): """Deployment runner for host installed python.""" @@ -368,8 +385,15 @@ def _setup_agent(self) -> None: def _get_host_deployment_runner(build_dir: Path) -> BaseDeploymentRunner: """Return depoyment runner according to running env.""" deployment_runner: BaseDeploymentRunner + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): - deployment_runner = PyInstallerHostDeploymentRunner(build_dir) + # pyinstaller inside! + if platform.system() == "Darwin": + deployment_runner = PyInstallerHostDeploymentRunner(build_dir) + elif platform.system() == "Windows": + deployment_runner = PyInstallerHostDeploymentRunnerWindows(build_dir) + else: + raise ValueError(f"Platform not supported {platform.system()}") else: deployment_runner = HostPythonHostDeploymentRunner(build_dir) return deployment_runner