From 6c2d93ba36fc5d154585ae1fa701940a8e0e4a09 Mon Sep 17 00:00:00 2001 From: Yostra Date: Thu, 21 May 2020 16:10:02 -0700 Subject: [PATCH] win insaller cleanup --- .github/workflows/windows-installer.yml | 42 +--- azure-pipelines.yml | 11 + build_scripts/build_mac.sh | 7 +- build_scripts/build_windows.ps1 | 39 ++++ build_scripts/daemon.spec | 2 +- build_scripts/daemon_windows.spec | 262 ++++++++++++++++++++++++ build_scripts/install_win.py | 6 + electron-react/package-lock.json | 86 +++++++- electron-react/package.json | 6 +- electron-react/src/assets/img/chia.icns | Bin 0 -> 5276 bytes electron-react/src/assets/img/chia.ico | Bin 0 -> 26704 bytes electron-react/src/electron-starter.js | 27 ++- electron-react/src/pages/NewWallet.js | 6 +- electron-react/src/setupEvents.js | 65 ++++++ electron-react/windows.json | 5 + electron-react/winstaller.js | 25 +++ src/daemon/server.py | 8 +- src/server/start_full_node.py | 2 + 18 files changed, 551 insertions(+), 48 deletions(-) create mode 100644 build_scripts/install_win.py create mode 100644 electron-react/src/assets/img/chia.icns create mode 100644 electron-react/src/assets/img/chia.ico create mode 100644 electron-react/src/setupEvents.js create mode 100644 electron-react/windows.json create mode 100644 electron-react/winstaller.js diff --git a/.github/workflows/windows-installer.yml b/.github/workflows/windows-installer.yml index 1adc71bc4736..00496c436f35 100644 --- a/.github/workflows/windows-installer.yml +++ b/.github/workflows/windows-installer.yml @@ -19,43 +19,19 @@ jobs: python-version: "3.7" - name: curl miniupnpc and setproctitle - working-directory: ${{ github.workspace }}\electron-wix\blockchain + working-directory: ${{ github.workspace }}\build_scripts run: | - curl -OL --show-error --fail https://download.chia.net/simple/miniupnpc/miniupnpc-2.1-cp37-cp37m-win_amd64.whl - curl -OL --show-error --fail https://download.chia.net/simple/setproctitle/setproctitle-1.1.10-cp37-cp37m-win_amd64.whl + git config --global core.longpaths true + ./build_windows.ps1 - - name: Collect and Create wheels for chia-blockchain - run: | - python -m pip install --upgrade pip - pip install pep517 wheel - pip wheel --use-pep517 --only-binary cbor2 --extra-index-url https://download.chia.net/simple/ -f ${{ github.workspace }} --wheel-dir=${{ github.workspace }}\electron-wix\blockchain ${{ github.workspace }}\. - - - name: Install electron-packager - run: | - npm install electron-packager -g - - - name: npm install - working-directory: ${{ github.workspace }}\electron-react - run: | - npm install --runtime=electron --target=1.7.6 - - - name: curl Visual C++ 2019 redistributable # Windows has curl natively - working-directory: ${{ github.workspace }}\electron-wix\prerequisites\ - run: curl -OL --show-error --fail https://aka.ms/vs/16/release/vc_redist.x64.exe - - - name: curl Python 3.7.7 Installer - working-directory: ${{ github.workspace }}\electron-wix\prerequisites\ - run: curl -OL --show-error --fail https://www.python.org/ftp/python/3.7.7/python-3.7.7-amd64.exe - - - name: Build Windows installer with Wix - env: - version: 0.1.13 # TODO Need to create this from setuptools_scm for Windows - working-directory: ${{ github.workspace }}\electron-wix - run: | - .\rebuild-all.ps1 + - name: Upload artifacts EXE + uses: actions/upload-artifact@v1 + with: + name: Windows-Exe + path: ${{ github.workspace }}\electron-react\Chia-win32-x64 - name: Upload artifacts uses: actions/upload-artifact@v1 with: name: Windows-Installers - path: ${{ github.workspace }}\electron-wix\final + path: ${{ github.workspace }}\electron-react\release-builds\ diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 175736f78380..990fbbda5287 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -69,8 +69,19 @@ steps: py.test tests -s -v displayName: "pytest" + - script: | + cd build_scripts + sh build_mac.sh + displayName: "Build DMG" + - task: PublishPipelineArtifact@1 inputs: targetPath: $(System.DefaultWorkingDirectory)/wheels artifactName: MacOS-wheels displayName: "Upload MacOS wheels" + + - task: PublishPipelineArtifact@1 + inputs: + targetPath: $(System.DefaultWorkingDirectory)/build_scripts/Chia.dmg + artifactName: MacOS-DMG + displayName: "Upload MacOS DMG" diff --git a/build_scripts/build_mac.sh b/build_scripts/build_mac.sh index 32961f85cd26..2d0000e55798 100644 --- a/build_scripts/build_mac.sh +++ b/build_scripts/build_mac.sh @@ -6,8 +6,9 @@ sudo pyinstaller daemon.spec cp -r dist/daemon ../electron-react cd .. cd electron-react +npm install npm run build -electron-packager . chia --overwrite -mv chia-darwin-x64 ../build_scripts/dist/ +electron-packager . Chia --overwrite --icon=./src/assets/img/chia.ico +mv Chia-darwin-x64 ../build_scripts/dist/ cd ../build_scripts -electron-installer-dmg dist/chia-darwin-x64/chia.app Chia \ No newline at end of file +electron-installer-dmg dist/Chia-darwin-x64/Chia.app Chia --overwrite diff --git a/build_scripts/build_windows.ps1 b/build_scripts/build_windows.ps1 index e69de29bb2d1..0270d93f0b13 100644 --- a/build_scripts/build_windows.ps1 +++ b/build_scripts/build_windows.ps1 @@ -0,0 +1,39 @@ +cd .. +mkdir build_scripts\win_build +cd build_scripts\win_build + +curl -OL --show-error --fail https://download.chia.net/simple/miniupnpc/miniupnpc-2.1-cp37-cp37m-win_amd64.whl +curl -OL --show-error --fail https://download.chia.net/simple/setproctitle/setproctitle-1.1.10-cp37-cp37m-win_amd64.whl +# C:\curl\curl.exe -OL --show-error --fail https://download.chia.net/simple/setproctitle/setproctitle-1.1.10-cp37-cp37m-win_amd64.whl +# C:\curl\curl.exe -OL --show-error --fail https://download.chia.net/simple/miniupnpc/miniupnpc-2.1-cp37-cp37m-win_amd64.whl + +Write-Output "checkpoint 1"; +cd ..\.. +python -m pip install --upgrade pip +pip install pep517 wheel +pip wheel --use-pep517 --only-binary cbor2 --extra-index-url https://download.chia.net/simple/ -f . --wheel-dir=.\build_scripts\win_build . +Write-Output "checkpoint 2"; + +python -m venv venv +. .\venv\Scripts\Activate.ps1 +python -m pip install --upgrade pip +cd build_scripts +python install_win.py +Write-Output "checkpoint 3"; + +pip install pywin32 +pip install pyinstaller +pyinstaller daemon_windows.spec +Write-Output "checkpoint 4"; + +cp -r dist/daemon ../electron-react/ +cd ../electron-react + +Write-Output "checkpoint 5"; + +npm install --save-dev electron-winstaller +npm install -g electron-packager +npm install +npm run build +electron-packager . Chia --asar.unpack="**/daemon/**" --overwrite --icon=./src/assets/img/chia.ico +node winstaller.js diff --git a/build_scripts/daemon.spec b/build_scripts/daemon.spec index e82f5e680542..b5bc2d823654 100644 --- a/build_scripts/daemon.spec +++ b/build_scripts/daemon.spec @@ -73,7 +73,7 @@ full_node = Analysis([f"{root}/src/server/start_full_node.py"], wallet = Analysis([f"{root}/src/wallet/websocket_server.py"], pathex=[f"{root}/venv/lib/python3.7/site-packages/aiter/", f"{root}"], binaries = [], - datas=[(f"{root}/src/util/bip39/english.txt", f"."), version_data ], + datas=[(f"{root}/src/util/bip39/english.txt", f"./src/util/bip39/"), version_data ], hiddenimports=subcommand_modules, hookspath=[], runtime_hooks=[], diff --git a/build_scripts/daemon_windows.spec b/build_scripts/daemon_windows.spec index e69de29bb2d1..c5e61b3a23f0 100644 --- a/build_scripts/daemon_windows.spec +++ b/build_scripts/daemon_windows.spec @@ -0,0 +1,262 @@ +# -*- mode: python ; coding: utf-8 -*- +#from src.cmds.chia import SUBCOMMANDS +import pathlib + +from PyInstaller.utils.hooks import collect_submodules, copy_metadata + +keyring_imports = collect_submodules('keyring.backends') + +# keyring uses entrypoints to read keyring.backends from metadata file entry_points.txt. +keyring_datas = copy_metadata('keyring')[0] + +from pkg_resources import get_distribution + +build = pathlib.Path().absolute() +root = build.parent + +from PyInstaller.utils.hooks import copy_metadata +version_data = copy_metadata(get_distribution("chia-blockchain"))[0] + +SUBCOMMANDS = [ + "init", + "keys", + "show", + "start", + "stop", + "version", + "netspace", + "run_daemon", +] +block_cipher = None +subcommand_modules = [f"{root}/src.cmds.%s" % _ for _ in SUBCOMMANDS] +other = ["aiter.active_aiter", "aiter.aiter_forker", "aiter.aiter_to_iter", "aiter.azip", "aiter.flatten_aiter", "aiter.gated_aiter", +"aiter.iter_to_aiter", "aiter.join_aiters", "aiter.map_aiter", "aiter.map_filter_aiter", "aiter.preload_aiter", +"aiter.push_aiter", "aiter.sharable_aiter", "aiter.stoppable_aiter","src.wallet.websocket_server", "win32cred", "pywintypes", "win32ctypes.pywin32"] + +entry_points = ["aiohttp", "aiohttp.aiohttp", + "src.cmds.check_plots", + "src.cmds.create_plots", + "src.wallet.websocket_server", + "src.server.start_full_node", + "src.server.start_harvester", + "src.server.start_farmer", + "src.server.start_introducer", + "src.server.start_timelord", + "src.timelord_launcher", + "src.simulator.start_simulator", + "src.util.bip32"] + +subcommand_modules.extend(other) +subcommand_modules.extend(entry_points) +subcommand_modules.extend(keyring_imports) + +daemon = Analysis([f"{root}/src/daemon/server.py"], + pathex=[f"{root}/venv/lib/python3.7/site-packages/aiter/", f"{root}"], + binaries = [(f"{root}/venv\Lib\site-packages\\*dll", '.')], + datas=[keyring_datas, version_data, (f"../src/util/initial-config.yaml", f"./src/util/"), + (f"../src/util/initial-plots.yaml", f"./src/util/") ], + hiddenimports=subcommand_modules, + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +full_node = Analysis([f"{root}/src/server/start_full_node.py"], + pathex=[f"{root}/venv/lib/python3.7/site-packages/aiter/", f"{root}"], + binaries = [], + datas=[version_data], + hiddenimports=subcommand_modules, + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +wallet = Analysis([f"{root}/src/wallet/websocket_server.py"], + pathex=[f"{root}/venv/lib/python3.7/site-packages/aiter/", f"{root}"], + binaries = [], + datas=[(f"{root}/src/util/bip39/english.txt", f"./src/util/bip39/"), version_data ], + hiddenimports=subcommand_modules, + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +plotter = Analysis([f"{root}/src/cmds/create_plots.py"], + pathex=[f"{root}/venv/lib/python3.7/site-packages/aiter/", f"{root}"], + binaries = [], + datas=[version_data], + hiddenimports=subcommand_modules, + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +farmer = Analysis([f"{root}/src/server/start_farmer.py"], + pathex=[f"{root}/venv/lib/python3.7/site-packages/aiter/", f"{root}"], + binaries = [], + datas=[version_data], + hiddenimports=subcommand_modules, + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +harvester = Analysis([f"{root}/src/server/start_harvester.py"], + pathex=[f"{root}/venv/lib/python3.7/site-packages/aiter/", f"{root}"], + binaries = [], + datas=[version_data], + hiddenimports=subcommand_modules, + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +check_plots = Analysis([f"{root}/src/cmds/check_plots.py"], + pathex=[f"{root}/venv/lib/python3.7/site-packages/aiter/", f"{root}"], + binaries = [], + datas=[version_data], + hiddenimports=subcommand_modules, + hookspath=[], + runtime_hooks=[], + excludes=[], + win_no_prefer_redirects=False, + win_private_assemblies=False, + cipher=block_cipher, + noarchive=False) + +daemon_pyz = PYZ(daemon.pure, daemon.zipped_data, + cipher=block_cipher) +full_node_pyz = PYZ(full_node.pure, full_node.zipped_data, + cipher=block_cipher) +wallet_pyz = PYZ(wallet.pure, wallet.zipped_data, + cipher=block_cipher) +plotter_pyz = PYZ(plotter.pure, plotter.zipped_data, + cipher=block_cipher) +farmer_pyz = PYZ(farmer.pure, farmer.zipped_data, + cipher=block_cipher) +harvester_pyz = PYZ(harvester.pure, harvester.zipped_data, + cipher=block_cipher) +check_plots_pyz = PYZ(check_plots.pure, check_plots.zipped_data, + cipher=block_cipher) + +daemon_exe = EXE(daemon_pyz, + daemon.scripts, + [], + exclude_binaries=True, + name='daemon', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=True ) + +full_node_exe = EXE(full_node_pyz, + full_node.scripts, + [], + exclude_binaries=True, + name='start_full_node', + debug=False, + bootloader_ignore_signals=False, + strip=False) + +wallet_exe = EXE(wallet_pyz, + wallet.scripts, + [], + exclude_binaries=True, + name='websocket_server', + debug=False, + bootloader_ignore_signals=False, + strip=False) + +plotter_exe = EXE(plotter_pyz, + plotter.scripts, + [], + exclude_binaries=True, + name='create_plots', + debug=False, + bootloader_ignore_signals=False, + strip=False) + +farmer_exe = EXE(farmer_pyz, + farmer.scripts, + [], + exclude_binaries=True, + name='start_farmer', + debug=False, + bootloader_ignore_signals=False, + strip=False) + +harvester_exe = EXE(harvester_pyz, + farmer.scripts, + [], + exclude_binaries=True, + name='start_harvester', + debug=False, + bootloader_ignore_signals=False, + strip=False) + +check_plots_exe = EXE(check_plots_pyz, + check_plots.scripts, + [], + exclude_binaries=True, + name='start_harvester', + debug=False, + bootloader_ignore_signals=False, + strip=False) + +coll = COLLECT(daemon_exe, + daemon.binaries, + daemon.zipfiles, + daemon.datas, + + full_node_exe, + full_node.binaries, + full_node.zipfiles, + full_node.datas, + + wallet_exe, + wallet.binaries, + wallet.zipfiles, + wallet.datas, + + plotter_exe, + plotter.binaries, + plotter.zipfiles, + plotter.datas, + + farmer_exe, + farmer.binaries, + farmer.zipfiles, + farmer.datas, + + harvester_exe, + harvester.binaries, + harvester.zipfiles, + harvester.datas, + + check_plots_exe, + check_plots.binaries, + check_plots.zipfiles, + check_plots.datas, + strip = False, + upx_exclude = [], + name = 'daemon' +) diff --git a/build_scripts/install_win.py b/build_scripts/install_win.py new file mode 100644 index 000000000000..7c8a597439e5 --- /dev/null +++ b/build_scripts/install_win.py @@ -0,0 +1,6 @@ +import os +import subprocess +import sys + +for filename in os.listdir('.\win_build'): + subprocess.check_call([sys.executable, "-m", "pip", "install", f".\win_build\{filename}"]) diff --git a/electron-react/package-lock.json b/electron-react/package-lock.json index fe96865229d6..80ff8c164c7f 100644 --- a/electron-react/package-lock.json +++ b/electron-react/package-lock.json @@ -1,5 +1,5 @@ { - "name": "chia-wallet", + "name": "Chia", "version": "0.1.0", "lockfileVersion": 1, "requires": true, @@ -2198,6 +2198,22 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "asar": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/asar/-/asar-2.1.0.tgz", + "integrity": "sha512-d2Ovma+bfqNpvBzY/KU8oPY67ZworixTpkjSx0PCXnQi67c2cXmssaTxpFDUM0ttopXoGx/KRxNg/GDThYbXQA==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "chromium-pickle-js": "^0.2.0", + "commander": "^2.20.0", + "cuint": "^0.2.2", + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "tmp-promise": "^1.0.5" + } + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -3342,6 +3358,12 @@ "tslib": "^1.9.0" } }, + "chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha1-BKEGZywYsIWrd02YPfo+oTjyIgU=", + "dev": true + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -4087,6 +4109,12 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.10.tgz", "integrity": "sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w==" }, + "cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=", + "dev": true + }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", @@ -4552,6 +4580,32 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.425.tgz", "integrity": "sha512-JTEOWiqCY4snuKuQAaFy0z6LK2Gdb8Lojkd/csQwpNHgMUF8I6QRjGVKk44IH46dHQhUFKzr4o6zxZrtDBjc2Q==" }, + "electron-winstaller": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-4.0.0.tgz", + "integrity": "sha512-Rq5YUQ/zBiGiDW3ezVaRigF3QbohVjDtfcpZpzmhJxX/1jndc96OQJw2x1HulHmhPV7n9R4WEsMkzkHObufU9g==", + "dev": true, + "requires": { + "asar": "^2.0.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash.template": "^4.2.2", + "temp": "^0.9.0" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + } + } + }, "elliptic": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", @@ -13826,6 +13880,15 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" }, + "temp": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.1.tgz", + "integrity": "sha512-WMuOgiua1xb5R56lE0eH6ivpVmg/lq2OHm4+LtT/xtEtPQ+sz6N3bBM6WZ5FvO1lO4IKIOb43qnhoc4qxP5OeA==", + "dev": true, + "requires": { + "rimraf": "~2.6.2" + } + }, "terser": { "version": "4.6.13", "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.13.tgz", @@ -14033,6 +14096,27 @@ "os-tmpdir": "~1.0.2" } }, + "tmp-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-1.1.0.tgz", + "integrity": "sha512-8+Ah9aB1IRXCnIOxXZ0uFozV1nMU5xiu7hhFVUSxZ3bYu+psD4TzagCzVbexUCgNNGJnsmNDQlS4nG3mTyoNkw==", + "dev": true, + "requires": { + "bluebird": "^3.5.0", + "tmp": "0.1.0" + }, + "dependencies": { + "tmp": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz", + "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==", + "dev": true, + "requires": { + "rimraf": "^2.6.3" + } + } + } + }, "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", diff --git a/electron-react/package.json b/electron-react/package.json index 2ce993e0b4ab..21b63caab781 100644 --- a/electron-react/package.json +++ b/electron-react/package.json @@ -1,8 +1,12 @@ { - "name": "chia-wallet", + "name": "Chia", + "author": "Straya Markovic", + "description": "GUI for Chia Blockchain", + "productName": "Chia", "version": "0.1.0", "private": true, "devDependencies": { + "electron-winstaller": "^4.0.0", "foreman": "^3.0.1", "prettier-eslint": "^9.0.1", "prettier-eslint-cli": "^5.0.0", diff --git a/electron-react/src/assets/img/chia.icns b/electron-react/src/assets/img/chia.icns new file mode 100644 index 0000000000000000000000000000000000000000..8b913ef1e6cfd231174a9879a4de306732dd50e8 GIT binary patch literal 5276 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 { - const fullPath = path.join(__dirname, PY_BUILD_FOLDER); + if (process.platform === "win32") { + const fullPath = path.join(__dirname, PY_WIN_DIST_FOLDER); + packed = require("fs").existsSync(fullPath); + console.log(fullPath); + console.log(packed); + return packed + } + const fullPath = path.join(__dirname, PY_MAC_DIST_FOLDER); packed = require("fs").existsSync(fullPath); console.log(fullPath); + console.log(packed); return packed; }; @@ -41,9 +56,9 @@ const getScriptPath = () => { return path.join(PY_FOLDER, PY_MODULE + ".py"); } if (process.platform === "win32") { - return path.join(__dirname, PY_DIST_FOLDER, PY_DIST_FILE + ".exe"); + return path.join(__dirname, PY_WIN_DIST_FOLDER, PY_DIST_FILE + ".exe"); } - return path.join(__dirname, PY_DIST_FOLDER, PY_DIST_FILE); + return path.join(__dirname, PY_MAC_DIST_FOLDER, PY_DIST_FILE); }; const createPyProc = () => { @@ -63,6 +78,8 @@ const createPyProc = () => { } } else { console.log("Running python script"); + console.log("Script " + script); + const Process = require("child_process").spawn; pyProc = new Process("python", [script], processOptions); } diff --git a/electron-react/src/pages/NewWallet.js b/electron-react/src/pages/NewWallet.js index 312be3c26e4e..1ed2912444a9 100644 --- a/electron-react/src/pages/NewWallet.js +++ b/electron-react/src/pages/NewWallet.js @@ -42,10 +42,12 @@ const Iterator = props => { }; const UIPart = props => { - const words = useSelector(state => state.wallet_state.mnemonic); + var words = useSelector(state => state.wallet_state.mnemonic); const dispatch = useDispatch(); const classes = myStyle(); - + if (!words) { + words = [] + } function goBack() { dispatch(changeEntranceMenu(presentSelectKeys)); } diff --git a/electron-react/src/setupEvents.js b/electron-react/src/setupEvents.js new file mode 100644 index 000000000000..21df5237c9d9 --- /dev/null +++ b/electron-react/src/setupEvents.js @@ -0,0 +1,65 @@ +const electron = require('electron') +const app = electron.app + +module.exports = { +handleSquirrelEvent: function() { + if (process.argv.length === 1) { + return false; + } + + const ChildProcess = require('child_process'); + const path = require('path'); + + const appFolder = path.resolve(process.execPath, '..'); + const rootAtomFolder = path.resolve(appFolder, '..'); + const updateDotExe = path.resolve(path.join(rootAtomFolder, 'Update.exe')); + const exeName = path.basename(process.execPath); + const spawn = function(command, args) { + let spawnedProcess, error; + + try { + spawnedProcess = ChildProcess.spawn(command, args, {detached: true}); + } catch (error) {} + + return spawnedProcess; + }; + + const spawnUpdate = function(args) { + return spawn(updateDotExe, args); + }; + + const squirrelEvent = process.argv[1]; +switch (squirrelEvent) { + case '--squirrel-install': + case '--squirrel-updated': + // Optionally do things such as: + // - Add your .exe to the PATH + // - Write to the registry for things like file associations and + // explorer context menus + + // Install desktop and start menu shortcuts + spawnUpdate(['--createShortcut', exeName]); + + setTimeout(app.quit, 1000); + return true; + + case '--squirrel-uninstall': + // Undo anything you did in the --squirrel-install and + // --squirrel-updated handlers + + // Remove desktop and start menu shortcuts + spawnUpdate(['--removeShortcut', exeName]); + + setTimeout(app.quit, 1000); + return true; + + case '--squirrel-obsolete': + // This is called on the outgoing version of your app before + // we update to the new version - it's the opposite of + // --squirrel-updated + + app.quit(); + return true; +} +} +} diff --git a/electron-react/windows.json b/electron-react/windows.json new file mode 100644 index 000000000000..03adc7c908c4 --- /dev/null +++ b/electron-react/windows.json @@ -0,0 +1,5 @@ +{ + "dest": "dist/installers/", + "icon": "src/assets/img/chia.ico", + "tags": ["Blockchain"] +} diff --git a/electron-react/winstaller.js b/electron-react/winstaller.js new file mode 100644 index 000000000000..71acbd9f4c8f --- /dev/null +++ b/electron-react/winstaller.js @@ -0,0 +1,25 @@ +const createWindowsInstaller = require('electron-winstaller').createWindowsInstaller +const path = require('path') + +getInstallerConfig() + .then(createWindowsInstaller) + .catch((error) => { + console.error(error.message || error) + process.exit(1) + }) + +function getInstallerConfig () { + console.log('creating windows installer') + const rootPath = path.join('./') + const outPath = path.join(rootPath, 'release-builds') + + return Promise.resolve({ + appDirectory: path.join(rootPath, 'Chia-win32-x64/'), + authors: 'Chia Networks', + noMsi: true, + outputDirectory: path.join(outPath, 'windows-installer'), + exe: 'Chia.exe', + setupExe: 'ChiaSetup.exe', + setupIcon: path.join(rootPath, 'src', 'assets', 'img', 'chia.ico') + }) +} diff --git a/src/daemon/server.py b/src/daemon/server.py index 9c64b4d0a967..728aa1eb0995 100644 --- a/src/daemon/server.py +++ b/src/daemon/server.py @@ -313,6 +313,10 @@ def launch_service(root_path, service_command): service_name = service_array[0] service_executable = executable_for_service(service_name) service_array[0] = service_executable + startupinfo = None + if os.name == 'nt': + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW if service_name == "chia-create-plots": plotter_path = plotter_log_path(root_path) if plotter_path.parent.exists(): @@ -321,9 +325,9 @@ def launch_service(root_path, service_command): else: mkdir(plotter_path.parent) outfile = open(plotter_path.resolve(), "w") - process = subprocess.Popen(service_array, shell=False, stdout=outfile) + process = subprocess.Popen(service_array, shell=False, stdout=outfile, startupinfo=startupinfo) else: - process = subprocess.Popen(service_array, shell=False) + process = subprocess.Popen(service_array, shell=False, startupinfo=startupinfo) pid_path = pid_path_for_service(root_path, service_command) try: mkdir(pid_path.parent) diff --git a/src/server/start_full_node.py b/src/server/start_full_node.py index 8a660a791084..085b04bb0396 100644 --- a/src/server/start_full_node.py +++ b/src/server/start_full_node.py @@ -17,6 +17,7 @@ from src.util.config import load_config_cli, load_config from src.util.default_root import DEFAULT_ROOT_PATH from src.util.setproctitle import setproctitle +from multiprocessing import freeze_support async def async_main(): @@ -109,4 +110,5 @@ def main(): if __name__ == "__main__": + freeze_support() main()