From 2564e63e7576eb82b6a641662157908abc346d25 Mon Sep 17 00:00:00 2001 From: e-halinen <54105602+e-halinen@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:20:05 +0300 Subject: [PATCH] WIP: #205 better support for multiple EMME python versions --- src/renderer/components/App.jsx | 21 +++++++++- src/renderer/components/Settings/Settings.css | 28 ++++++++++++++ src/renderer/components/Settings/Settings.jsx | 33 ++++++++++++---- src/renderer/search_emme_pythonpath.js | 38 +++++++++++++++++++ 4 files changed, 111 insertions(+), 9 deletions(-) diff --git a/src/renderer/components/App.jsx b/src/renderer/components/App.jsx index 5cd09c0e..88e1ac3c 100644 --- a/src/renderer/components/App.jsx +++ b/src/renderer/components/App.jsx @@ -8,13 +8,13 @@ const {execSync} = require('child_process'); const path = require('path'); // vex-js imported globally in index.html, since we cannot access webpack config in electron-forge - const App = ({helmetUIVersion, versions, searchEMMEPython}) => { // Global settings const [isSettingsOpen, setSettingsOpen] = useState(false); // whether Settings -dialog is open const [isProjectRunning, setProjectRunning] = useState(false); // whether currently selected Project is running const [emmePythonPath, setEmmePythonPath] = useState(undefined); // file path to EMME python executable + const [emmePythonEnvs, setEmmePythonEnvs] = useState([]); // List of all discovered python executables const [helmetScriptsPath, setHelmetScriptsPath] = useState(undefined); // folder path to HELMET model system scripts const [projectPath, setProjectPath] = useState(undefined); // folder path to scenario configs, default homedir const [basedataPath, setBasedataPath] = useState(undefined); // folder path to base input data (subdirectories: 2016_zonedata, 2016_basematrices) @@ -30,6 +30,20 @@ const App = ({helmetUIVersion, versions, searchEMMEPython}) => { globalSettingsStore.current.set('emme_python_path', newPath); }; + const _setEMMEPythonEnvs = (newList) => { + setEmmePythonEnvs(newList); + globalSettingsStore.current.set('emme_python_envs', newList); + } + + const _removeFromEMMEPythonEnvs = (path) => { + const pythonEnvs = emmePythonEnvs.slice(); + const foundPath = pythonEnvs.indexOf(path); + if (foundPath > -1) { + pythonEnvs.splice(foundPath,1); + _setEMMEPythonEnvs(pythonEnvs); + } + } + const _setHelmetScriptsPath = (newPath) => { // Cannot use state variable since it'd be undefined at times const pythonPath = globalSettingsStore.current.get('emme_python_path'); @@ -121,11 +135,13 @@ const App = ({helmetUIVersion, versions, searchEMMEPython}) => { const existingProjectPath = globalSettingsStore.current.get('project_path'); const existingBasedataPath = globalSettingsStore.current.get('basedata_path'); const existingResultsPath = globalSettingsStore.current.get('resultdata_path'); + const existingPythonEnvs = globalSettingsStore.current.get('emme_python_envs'); setEmmePythonPath(existingEmmePythonPath); setHelmetScriptsPath(existingHelmetScriptsPath); setProjectPath(existingProjectPath); setBasedataPath(existingBasedataPath); setResultsPath(existingResultsPath); + setEmmePythonEnvs(existingPythonEnvs); // If project path is the initial (un-set), set it to homedir. Remember: state updates async so refer to existing. if (!existingProjectPath) { @@ -159,6 +175,7 @@ const App = ({helmetUIVersion, versions, searchEMMEPython}) => {
{ isDownloadingHelmetScripts={isDownloadingHelmetScripts} closeSettings={() => setSettingsOpen(false)} setEMMEPythonPath={_setEMMEPythonPath} + setEMMEPythonEnvs={_setEMMEPythonEnvs} + removeFromEMMEPythonEnvs={_removeFromEMMEPythonEnvs} setHelmetScriptsPath={_setHelmetScriptsPath} setProjectPath={_setProjectPath} setBasedataPath={_setBasedataPath} diff --git a/src/renderer/components/Settings/Settings.css b/src/renderer/components/Settings/Settings.css index 67aa77f2..391cb18f 100644 --- a/src/renderer/components/Settings/Settings.css +++ b/src/renderer/components/Settings/Settings.css @@ -99,3 +99,31 @@ height: 40px; font-size: 16px; } + +.Settings__environment_option { + display: flex; + width: 100%; + padding: 10px 12px; + margin-top: 4px; + background: #ffffff; + box-sizing: border-box; + border-radius: 5px; + font-size: 16px; + font-weight: 325; + color: #333333; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: inherit; + min-height: 2rem; + align-items: center; + justify-items: center; + flex-wrap: wrap; +} + +.Settings__env_option_btn { + width: 4rem; + height: 2.5rem; + border-radius: 5px; + margin-left: 1.5rem; +} diff --git a/src/renderer/components/Settings/Settings.jsx b/src/renderer/components/Settings/Settings.jsx index 6a92a00b..177916be 100644 --- a/src/renderer/components/Settings/Settings.jsx +++ b/src/renderer/components/Settings/Settings.jsx @@ -1,11 +1,23 @@ import React from 'react'; import path from "path"; const {dialog} = require('@electron/remote'); -import {searchEMMEPython} from './search_emme_pythonpath'; import versions from '../versions'; +import { listEMMEPythonPaths } from './search_emme_pythonpath'; + +const EnvironmentOption = ({ + envPath, isSelected, setPath, removePath, +}) => { + return ( +
+

{envPath}

+ + +
+ ) +} const Settings = ({ - emmePythonPath, setEMMEPythonPath, + emmePythonPath, emmePythonEnvs, setEMMEPythonPath, setEMMEPythonEnvs, removeFromEMMEPythonEnvs, helmetScriptsPath, setHelmetScriptsPath, dlHelmetScriptsVersion, isDownloadingHelmetScripts, projectPath, setProjectPath, basedataPath, setBasedataPath, @@ -24,7 +36,12 @@ const Settings = ({
Projektin asetukset
- Emme Python v3.7 + Valittu Python-ympäristö: {emmePythonPath} + Käytettävät Python-ympäristöt: + { emmePythonEnvs.map(env => { return }) } +
@@ -46,18 +63,18 @@ const Settings = ({ }) }} /> +
diff --git a/src/renderer/search_emme_pythonpath.js b/src/renderer/search_emme_pythonpath.js index b6c1a654..dc3ef21a 100644 --- a/src/renderer/search_emme_pythonpath.js +++ b/src/renderer/search_emme_pythonpath.js @@ -41,6 +41,43 @@ const searchEMMEPython = () => { } }; +const listEMMEPythonPaths = () => { + // Set Windows' python exe path postfix (e.g. Python27\python.exe) + const p = getVersion(versions.emme_python); + const pythonPathPostfix = `Python${p.major}${p.minor}\\python.exe`; + + // Search from environment variable "EMMEPATH" + const envEmmePath = process.env.EMMEPATH || ''; + const envEmmePythonPath = path.join(envEmmePath, pythonPathPostfix); + if (envEmmePath && fs.existsSync(envEmmePythonPath)) { + return [true, envEmmePythonPath]; + } + + // Not found based on EMMEPATH, try guessing some common locations on Windows + const e = getVersion(versions.emme_system); + const pythonVersion = getVersion(versions.emme_python); + const commonEmmePath = `INRO\\Emme\\Emme ${e.major}\\Emme-${e.semver}`; + const drives = ['C:', 'D:', 'E:', 'F:', 'G:', 'H:', 'I:', 'J:', '/']; + const paths = [ + `\\Program Files\\${commonEmmePath}\\${pythonPathPostfix}`, + `\\Program Files (x86)\\${commonEmmePath}\\${pythonPathPostfix}`, + `\\${commonEmmePath}\\${pythonPathPostfix}`, + `usr/bin/python${pythonVersion.major}`, // mainly for developers on Mac & Linux + `Users/erkki/pyyttoni/testi/testikansio/testiloremipsum2.34.5`, + ]; + const allPathCombinations = drives.reduce( + (accumulator, d) => { + // Combine each (d)rive to all (p)aths, and merge results via reduce + return accumulator.concat(paths.map((p) => `${d}${p}`)); + }, []); + const pythonInstallations = allPathCombinations.filter(fs.existsSync); + if (pythonInstallations.length > 0) { + return [true, pythonInstallations]; + } else { + return [false, null]; + } +} + /** * Dissect given semantic version number string */ @@ -56,4 +93,5 @@ function getVersion(semver) { module.exports = { searchEMMEPython: searchEMMEPython, + listEMMEPythonPaths: listEMMEPythonPaths, };