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,
};