From d8b478252468b06e5eb6bbc04a5437c1b55f6efe Mon Sep 17 00:00:00 2001 From: Julien Marrec Date: Tue, 4 Jun 2024 11:02:40 +0200 Subject: [PATCH 01/14] POC integrating Python utilities into our install/CLI --- src/EnergyPlus/CMakeLists.txt | 7 +- src/EnergyPlus/CommandLineInterface.cc | 65 +++++ src/EnergyPlus/PluginManager.cc | 13 + src/EnergyPlus/PluginManager.hh | 14 +- src/EnergyPlus/PythonEngine.cc | 381 +++++++++++++++++++++++++ src/EnergyPlus/PythonEngine.hh | 86 ++++++ 6 files changed, 555 insertions(+), 11 deletions(-) create mode 100644 src/EnergyPlus/PythonEngine.cc create mode 100644 src/EnergyPlus/PythonEngine.hh diff --git a/src/EnergyPlus/CMakeLists.txt b/src/EnergyPlus/CMakeLists.txt index ff4693c57f3..92872612414 100644 --- a/src/EnergyPlus/CMakeLists.txt +++ b/src/EnergyPlus/CMakeLists.txt @@ -521,6 +521,8 @@ set(SRC Pumps.hh PurchasedAirManager.cc PurchasedAirManager.hh + PythonEngine.cc + PythonEngine.hh RefrigeratedCase.cc RefrigeratedCase.hh ReportCoilSelection.cc @@ -965,7 +967,10 @@ if(LINK_WITH_PYTHON) add_custom_command( TARGET energyplusapi POST_BUILD # TODO: I don't think we want to quote the generator expression - COMMAND ${Python_EXECUTABLE} "${PROJECT_SOURCE_DIR}/cmake/PythonCopyStandardLib.py" "$" "python_standard_lib") + COMMAND ${Python_EXECUTABLE} "${PROJECT_SOURCE_DIR}/cmake/PythonCopyStandardLib.py" "$" "python_standard_lib" + COMMAND ${CMAKE_COMMAND} -E env --unset=PIP_REQUIRE_VIRTUALENV + ${Python_EXECUTABLE} -m pip install --target="$/python_standard_lib" --upgrade energyplus-python-apps==24.1a1 + ) endif() if(BUILD_PACKAGE) diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index e54680f8a5b..e88b87caf4c 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -61,6 +61,10 @@ #include #include +#if LINK_WITH_PYTHON +#include +#endif + namespace EnergyPlus { namespace CommandLineInterface { @@ -211,6 +215,67 @@ Built on Platform: {} bool debugCLI = false; app.add_flag("--debug-cli", debugCLI, "Print the result of the CLI assignments to the console and exit")->group(""); // Empty group to hide it +#if LINK_WITH_PYTHON + static constexpr std::array logLevelStrs = {"Trace", "Debug", "Info", "Warn", "Error", "Fatal"}; + + auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools"); + + enum class ValidAuxiliaryTools + { + energyplus_iddidf, + eplaunch + + }; + + std::vector python_fwd_args; + auto *transitionSubcommand = auxiliaryToolsSubcommand->add_subcommand("energyplus_iddidf", "EnergyPlus Python IDD/IDF Utilities"); + transitionSubcommand->add_option("args", python_fwd_args, "Extra Arguments forwarded to the Python script")->option_text("ARG ..."); + transitionSubcommand->positionals_at_end(true); + transitionSubcommand->footer("You can pass extra arguments after the Python file, they will be forwarded."); + + transitionSubcommand->callback([&state, &python_fwd_args] { + EnergyPlus::Python::PythonEngine engine(state); + // There's probably better to be done, like instantiating the pythonEngine with the argc/argv then calling PyRun_SimpleFile but whatever + std::string cmd = R"python(import sys +sys.argv.clear() +sys.argv.append("energyplus") +)python"; + for (const auto &arg : python_fwd_args) { + cmd += fmt::format("sys.argv.append(\"{}\")\n", arg); + } + + cmd += R"python( +from energyplus_iddidf.cli import main_cli +main_cli() +)python"; + + engine.exec(cmd); + exit(0); + }); + + auto *epLaunchSubcommand = auxiliaryToolsSubcommand->add_subcommand("eplaunch", "EP-Launch"); + epLaunchSubcommand->callback([&state, &python_fwd_args] { + EnergyPlus::Python::PythonEngine engine(state); + // There's probably better to be done, like instantiating the pythonEngine with the argc/argv then calling PyRun_SimpleFile but whatever + std::string cmd = R"python(import sys +sys.argv.clear() +sys.argv.append("energyplus") +)python"; + for (const auto &arg : python_fwd_args) { + cmd += fmt::format("sys.argv.append(\"{}\")\n", arg); + } + + cmd += R"python( +from eplaunch.tk_runner import main_gui +main_gui() +)python"; + + engine.exec(cmd); + exit(0); + }); + +#endif + app.footer("Example: energyplus -w weather.epw -r input.idf"); const bool eplusRunningViaAPI = state.dataGlobal->eplusRunningViaAPI; diff --git a/src/EnergyPlus/PluginManager.cc b/src/EnergyPlus/PluginManager.cc index 51a09c452f3..02b84078000 100644 --- a/src/EnergyPlus/PluginManager.cc +++ b/src/EnergyPlus/PluginManager.cc @@ -58,6 +58,19 @@ #include #if LINK_WITH_PYTHON + +#ifdef _DEBUG +// We don't want to try to import a debug build of Python here +// so if we are building a Debug build of the C++ code, we need +// to undefine _DEBUG during the #include command for Python.h. +// Otherwise it will fail +#undef _DEBUG +#include +#define _DEBUG +#else +#include +#endif + #include namespace fmt { template <> struct formatter diff --git a/src/EnergyPlus/PluginManager.hh b/src/EnergyPlus/PluginManager.hh index 6bf6366ebdc..7001076affd 100644 --- a/src/EnergyPlus/PluginManager.hh +++ b/src/EnergyPlus/PluginManager.hh @@ -60,16 +60,9 @@ #include #if LINK_WITH_PYTHON -#ifdef _DEBUG -// We don't want to try to import a debug build of Python here -// so if we are building a Debug build of the C++ code, we need -// to undefine _DEBUG during the #include command for Python.h. -// Otherwise it will fail -#undef _DEBUG -#include -#define _DEBUG -#else -#include +#ifndef PyObject_HEAD +struct _object; +using PyObject = _object; #endif #endif @@ -179,6 +172,7 @@ namespace PluginManagement { #endif }; + // TODO: Make this use PythonEngine so we don't duplicate code class PluginManager { public: diff --git a/src/EnergyPlus/PythonEngine.cc b/src/EnergyPlus/PythonEngine.cc new file mode 100644 index 00000000000..109ea67a1ed --- /dev/null +++ b/src/EnergyPlus/PythonEngine.cc @@ -0,0 +1,381 @@ +// EnergyPlus, Copyright (c) 1996-2024, The Board of Trustees of the University of Illinois, +// The Regents of the University of California, through Lawrence Berkeley National Laboratory +// (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge +// National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other +// contributors. All rights reserved. +// +// NOTICE: This Software was developed under funding from the U.S. Department of Energy and the +// U.S. Government consequently retains certain rights. As such, the U.S. Government has been +// granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, +// worldwide license in the Software to reproduce, distribute copies to the public, prepare +// derivative works, and perform publicly and display publicly, and to permit others to do so. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// (1) Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// (2) Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory, +// the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific prior +// written permission. +// +// (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form +// without changes from the version obtained under this License, or (ii) Licensee makes a +// reference solely to the software portion of its product, Licensee must refer to the +// software as "EnergyPlus version X" software, where "X" is the version number Licensee +// obtained under this License and may not use a different name for the software. Except as +// specifically required in this Section (4), Licensee shall not use in a company name, a +// product name, in advertising, publicity, or other promotional activities any name, trade +// name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly +// similar designation, without the U.S. Department of Energy's prior written consent. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#include +#include +#include +#include + +#if LINK_WITH_PYTHON +#ifdef _DEBUG +// We don't want to try to import a debug build of Python here +// so if we are building a Debug build of the C++ code, we need +// to undefine _DEBUG during the #include command for Python.h. +// Otherwise it will fail +#undef _DEBUG +#include +#define _DEBUG +#else +#include +#endif + +#include +namespace fmt { +template <> struct formatter +{ + // parse is inherited from formatter. + constexpr auto parse(format_parse_context &ctx) -> format_parse_context::iterator + { + return ctx.begin(); + } + + auto format(const PyStatus &status, format_context &ctx) const -> format_context::iterator + { + if (PyStatus_Exception(status) == 0) { + return ctx.out(); + } + if (PyStatus_IsExit(status) != 0) { + return fmt::format_to(ctx.out(), "Exited with code {}", status.exitcode); + } + if (PyStatus_IsError(status) != 0) { + auto it = ctx.out(); + it = fmt::format_to(it, "Fatal Python error: "); + if (status.func) { + it = fmt::format_to(it, "{}: ", status.func); + } + it = fmt::format_to(it, "{}", status.err_msg); + return it; + } + return ctx.out(); + } +}; +} // namespace fmt + +#endif + +namespace EnergyPlus { + +namespace Python { + +#if LINK_WITH_PYTHON + + void reportPythonError([[maybe_unused]] EnergyPlusData &state) + { + PyObject *exc_type = nullptr; + PyObject *exc_value = nullptr; + PyObject *exc_tb = nullptr; + PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + // Normalizing the exception is needed. Without it, our custom EnergyPlusException go through just fine + // but any ctypes built-in exception for eg will have wrong types + PyErr_NormalizeException(&exc_type, &exc_value, &exc_tb); + PyObject *str_exc_value = PyObject_Repr(exc_value); // Now a unicode object + PyObject *pyStr2 = PyUnicode_AsEncodedString(str_exc_value, "utf-8", "Error ~"); + Py_DECREF(str_exc_value); + char *strExcValue = PyBytes_AsString(pyStr2); // NOLINT(hicpp-signed-bitwise) + Py_DECREF(pyStr2); + EnergyPlus::ShowContinueError(state, "Python error description follows: "); + EnergyPlus::ShowContinueError(state, strExcValue); + + // See if we can get a full traceback. + // Calls into python, and does the same as capturing the exception in `e` + // then `print(traceback.format_exception(e.type, e.value, e.tb))` + PyObject *pModuleName = PyUnicode_DecodeFSDefault("traceback"); + PyObject *pyth_module = PyImport_Import(pModuleName); + Py_DECREF(pModuleName); + + if (pyth_module == nullptr) { + EnergyPlus::ShowContinueError(state, "Cannot find 'traceback' module in reportPythonError(), this is weird"); + return; + } + + PyObject *pyth_func = PyObject_GetAttrString(pyth_module, "format_exception"); + Py_DECREF(pyth_module); // PyImport_Import returns a new reference, decrement it + + if (pyth_func || PyCallable_Check(pyth_func)) { + + PyObject *pyth_val = PyObject_CallFunction(pyth_func, "OOO", exc_type, exc_value, exc_tb); + + // traceback.format_exception returns a list, so iterate on that + if (!pyth_val || !PyList_Check(pyth_val)) { // NOLINT(hicpp-signed-bitwise) + EnergyPlus::ShowContinueError(state, "In reportPythonError(), traceback.format_exception did not return a list."); + return; + } + + Py_ssize_t numVals = PyList_Size(pyth_val); + if (numVals == 0) { + EnergyPlus::ShowContinueError(state, "No traceback available"); + return; + } + + EnergyPlus::ShowContinueError(state, "Python traceback follows: "); + + EnergyPlus::ShowContinueError(state, "```"); + + for (Py_ssize_t itemNum = 0; itemNum < numVals; itemNum++) { + PyObject *item = PyList_GetItem(pyth_val, itemNum); + if (PyUnicode_Check(item)) { // NOLINT(hicpp-signed-bitwise) -- something inside Python code causes warning + std::string traceback_line = PyUnicode_AsUTF8(item); + if (!traceback_line.empty() && traceback_line[traceback_line.length() - 1] == '\n') { + traceback_line.erase(traceback_line.length() - 1); + } + EnergyPlus::ShowContinueError(state, format(" >>> {}", traceback_line)); + } + // PyList_GetItem returns a borrowed reference, do not decrement + } + + EnergyPlus::ShowContinueError(state, "```"); + + // PyList_Size returns a borrowed reference, do not decrement + Py_DECREF(pyth_val); // PyObject_CallFunction returns new reference, decrement + } + Py_DECREF(pyth_func); // PyObject_GetAttrString returns a new reference, decrement it + } + + void addToPythonPath(EnergyPlusData &state, const fs::path &includePath, bool userDefinedPath) + { + if (includePath.empty()) { + return; + } + + // We use generic_string / generic_wstring here, which will always use a forward slash as directory separator even on windows + // This doesn't handle the (very strange, IMHO) case were on unix you have backlashes (which are VALID filenames on Unix!) + // Could use FileSystem::makeNativePath first to convert the backslashes to forward slashes on Unix + PyObject *unicodeIncludePath = nullptr; + if constexpr (std::is_same_v) { + const std::wstring ws = includePath.generic_wstring(); + unicodeIncludePath = PyUnicode_FromWideChar(ws.c_str(), static_cast(ws.size())); // New reference + } else { + const std::string s = includePath.generic_string(); + unicodeIncludePath = PyUnicode_FromString(s.c_str()); // New reference + } + if (unicodeIncludePath == nullptr) { + EnergyPlus::ShowFatalError( + state, format("ERROR converting the path \"{}\" for addition to the sys.path in Python", includePath.generic_string())); + } + + PyObject *sysPath = PySys_GetObject("path"); // Borrowed reference + int const ret = PyList_Insert(sysPath, 0, unicodeIncludePath); + Py_DECREF(unicodeIncludePath); + + if (ret != 0) { + if (PyErr_Occurred()) { + reportPythonError(state); + } + EnergyPlus::ShowFatalError(state, format("ERROR adding \"{}\" to the sys.path in Python", includePath.generic_string())); + } + + if (userDefinedPath) { + EnergyPlus::ShowMessage(state, format("Successfully added path \"{}\" to the sys.path in Python", includePath.generic_string())); + } + + // PyRun_SimpleString)("print(' EPS : ' + str(sys.path))"); + } + + void initPython(EnergyPlusData &state, fs::path const &pathToPythonPackages) + { + PyStatus status; + + // first pre-config Python so that it can speak UTF-8 + PyPreConfig preConfig; + // This is the other related line that caused Decent CI to start having trouble. I'm putting it back to + // PyPreConfig_InitPythonConfig, even though I think it should be isolated. Will deal with this after IO freeze. + PyPreConfig_InitPythonConfig(&preConfig); + // PyPreConfig_InitIsolatedConfig(&preConfig); + preConfig.utf8_mode = 1; + status = Py_PreInitialize(&preConfig); + if (PyStatus_Exception(status) != 0) { + ShowFatalError(state, fmt::format("Could not pre-initialize Python to speak UTF-8... {}", status)); + } + + PyConfig config; + PyConfig_InitIsolatedConfig(&config); + config.isolated = 1; + + status = PyConfig_SetBytesString(&config, &config.program_name, PluginManagement::programName); + if (PyStatus_Exception(status) != 0) { + ShowFatalError(state, fmt::format("Could not initialize program_name on PyConfig... {}", status)); + } + + status = PyConfig_Read(&config); + if (PyStatus_Exception(status) != 0) { + ShowFatalError(state, fmt::format("Could not read back the PyConfig... {}", status)); + } + + if constexpr (std::is_same_v) { + // PyConfig_SetString copies the wide character string str into *config_str. + std::wstring const ws = pathToPythonPackages.generic_wstring(); + const wchar_t *wcharPath = ws.c_str(); + + status = PyConfig_SetString(&config, &config.home, wcharPath); + if (PyStatus_Exception(status) != 0) { + ShowFatalError(state, fmt::format("Could not set home to {} on PyConfig... {}", pathToPythonPackages.generic_string(), status)); + } + status = PyConfig_SetString(&config, &config.base_prefix, wcharPath); + if (PyStatus_Exception(status) != 0) { + ShowFatalError(state, + fmt::format("Could not set base_prefix to {} on PyConfig... {}", pathToPythonPackages.generic_string(), status)); + } + config.module_search_paths_set = 1; + status = PyWideStringList_Append(&config.module_search_paths, wcharPath); + if (PyStatus_Exception(status) != 0) { + ShowFatalError( + state, fmt::format("Could not add {} to module_search_paths on PyConfig... {}", pathToPythonPackages.generic_string(), status)); + } + + } else { + // PyConfig_SetBytesString takes a `const char * str` and decodes str using Py_DecodeLocale() and set the result into *config_str + // But we want to avoid doing it three times, so we PyDecodeLocale manually + // Py_DecodeLocale can be called because Python has been PreInitialized. + wchar_t *wcharPath = Py_DecodeLocale(pathToPythonPackages.generic_string().c_str(), nullptr); // This allocates! + + status = PyConfig_SetString(&config, &config.home, wcharPath); + if (PyStatus_Exception(status) != 0) { + ShowFatalError(state, fmt::format("Could not set home to {} on PyConfig... {}", pathToPythonPackages.generic_string(), status)); + } + status = PyConfig_SetString(&config, &config.base_prefix, wcharPath); + if (PyStatus_Exception(status) != 0) { + ShowFatalError(state, + fmt::format("Could not set base_prefix to {} on PyConfig... {}", pathToPythonPackages.generic_string(), status)); + } + config.module_search_paths_set = 1; + status = PyWideStringList_Append(&config.module_search_paths, wcharPath); + if (PyStatus_Exception(status) != 0) { + ShowFatalError( + state, fmt::format("Could not add {} to module_search_paths on PyConfig... {}", pathToPythonPackages.generic_string(), status)); + } + + PyMem_RawFree(wcharPath); + } + + // This was Py_InitializeFromConfig(&config), but was giving a seg fault when running inside + // another Python instance, for example as part of an API run. Per the example here: + // https://docs.python.org/3/c-api/init_config.html#preinitialize-python-with-pypreconfig + // It looks like we don't need to initialize from config again, it should be all set up with + // the init calls above, so just initialize and move on. + // UPDATE: This worked happily for me on Linux, and also when I build locally on Windows, but not on Decent CI + // I suspect a difference in behavior for Python versions. I'm going to temporarily revert this back to initialize + // with config and get IO freeze going, then get back to solving it. + // Py_Initialize(); + Py_InitializeFromConfig(&config); + } + + PythonEngine::PythonEngine(EnergyPlusData &state) : eplusRunningViaPythonAPI(state.dataPluginManager->eplusRunningViaPythonAPI) + { + // we'll need the program directory for a few things so get it once here at the top and sanitize it + fs::path programDir; + if (state.dataGlobal->installRootOverride) { + programDir = state.dataStrGlobals->exeDirectoryPath; + } else { + programDir = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath())); + } + fs::path const pathToPythonPackages = programDir / "python_standard_lib"; + + initPython(state, pathToPythonPackages); + + // we also need to set an extra import path to find some dynamic library loading stuff, again make it relative to the binary + addToPythonPath(state, programDir / "python_standard_lib/lib-dynload", false); + + // now for additional paths: + // we'll always want to add the program executable directory to PATH so that Python can find the installed pyenergyplus package + // we will then optionally add the current working directory to allow Python to find scripts in the current directory + // we will then optionally add the directory of the running IDF to allow Python to find scripts kept next to the IDF + // we will then optionally add any additional paths the user specifies on the search paths object + + // so add the executable directory here + addToPythonPath(state, programDir, false); + + PyObject *m = PyImport_AddModule("__main__"); + if (m == nullptr) { + throw std::runtime_error("Unable to add module __main__ for python script execution"); + } + m_globalDict = PyModule_GetDict(m); + } + + void PythonEngine::exec(std::string_view sv) + { + std::string command{sv}; + + PyObject *v = PyRun_String(command.c_str(), Py_file_input, m_globalDict, m_globalDict); + // PyObject* v = PyRun_SimpleString(command.c_str()); + if (v == nullptr) { + PyErr_Print(); + throw std::runtime_error("Error executing Python code"); + } + + Py_DECREF(v); + } + + PythonEngine::~PythonEngine() + { + if (!this->eplusRunningViaPythonAPI) { + bool alreadyInitialized = (Py_IsInitialized() != 0); + if (alreadyInitialized) { + if (Py_FinalizeEx() < 0) { + exit(120); + } + } + } + } + +#else // NOT LINK_WITH_PYTHON + PythonEngine::PythonEngine() + { + ShowFatalError(state, "EnergyPlus is not linked with python"); + } + + PythonEngine::~PythonEngine() + { + } + + void PythonEngine::exec(std::string_view sv) + { + } + +#endif // LINK_WITH_PYTHON + +} // namespace Python +} // namespace EnergyPlus diff --git a/src/EnergyPlus/PythonEngine.hh b/src/EnergyPlus/PythonEngine.hh new file mode 100644 index 00000000000..41c4dd26cea --- /dev/null +++ b/src/EnergyPlus/PythonEngine.hh @@ -0,0 +1,86 @@ +// EnergyPlus, Copyright (c) 1996-2024, The Board of Trustees of the University of Illinois, +// The Regents of the University of California, through Lawrence Berkeley National Laboratory +// (subject to receipt of any required approvals from the U.S. Dept. of Energy), Oak Ridge +// National Laboratory, managed by UT-Battelle, Alliance for Sustainable Energy, LLC, and other +// contributors. All rights reserved. +// +// NOTICE: This Software was developed under funding from the U.S. Department of Energy and the +// U.S. Government consequently retains certain rights. As such, the U.S. Government has been +// granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, +// worldwide license in the Software to reproduce, distribute copies to the public, prepare +// derivative works, and perform publicly and display publicly, and to permit others to do so. +// +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: +// +// (1) Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// (2) Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials +// provided with the distribution. +// +// (3) Neither the name of the University of California, Lawrence Berkeley National Laboratory, +// the University of Illinois, U.S. Dept. of Energy nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific prior +// written permission. +// +// (4) Use of EnergyPlus(TM) Name. If Licensee (i) distributes the software in stand-alone form +// without changes from the version obtained under this License, or (ii) Licensee makes a +// reference solely to the software portion of its product, Licensee must refer to the +// software as "EnergyPlus version X" software, where "X" is the version number Licensee +// obtained under this License and may not use a different name for the software. Except as +// specifically required in this Section (4), Licensee shall not use in a company name, a +// product name, in advertising, publicity, or other promotional activities any name, trade +// name, trademark, logo, or other designation of "EnergyPlus", "E+", "e+" or confusingly +// similar designation, without the U.S. Department of Energy's prior written consent. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef EPLUS_PYTHON_ENGINE_HH +#define EPLUS_PYTHON_ENGINE_HH + +#include + +#if LINK_WITH_PYTHON +#ifndef PyObject_HEAD +struct _object; +using PyObject = _object; +#endif +#endif + +namespace EnergyPlus { + +namespace Python { + + class PythonEngine + { + public: + explicit PythonEngine(EnergyPlus::EnergyPlusData &state); + PythonEngine(const PythonEngine &) = delete; + PythonEngine(PythonEngine &&) = delete; + PythonEngine &operator=(const PythonEngine &) = delete; + PythonEngine &operator=(PythonEngine &&) = delete; + ~PythonEngine(); + + void exec(std::string_view sv); + + bool eplusRunningViaPythonAPI = false; + + private: +#if LINK_WITH_PYTHON + PyObject *m_globalDict; +#endif + }; +} // namespace Python +} // namespace EnergyPlus + +#endif // EPLUS_PYTHON_ENGINE_HH From 98faae2917121ec3073012cfe21f8b515eab1d06 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Tue, 3 Sep 2024 14:38:20 -0500 Subject: [PATCH 02/14] Rename python_standard_lib to python_lib; fix auxiliary argument issue --- cmake/PythonCopyStandardLib.py | 2 +- cmake/install_codesign_script.cmake | 6 +++--- src/EnergyPlus/CMakeLists.txt | 6 +++--- src/EnergyPlus/CommandLineInterface.cc | 2 +- src/EnergyPlus/PluginManager.cc | 4 ++-- src/EnergyPlus/PythonEngine.cc | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cmake/PythonCopyStandardLib.py b/cmake/PythonCopyStandardLib.py index 1e4becce5bb..7fabbef4b47 100644 --- a/cmake/PythonCopyStandardLib.py +++ b/cmake/PythonCopyStandardLib.py @@ -61,7 +61,7 @@ # this script must be called with two args: # 1 - the path to the EnergyPlus executable in the install-tree, which is used to determine where to copy the library # since this is in the install-tree, you'll need to use a cmake generator expression -# 2 - name of the folder to create to store the copied in python standard library, usually python_standard_library +# 2 - name of the folder to create to store the copied in python standard library, usually python_library import ctypes import os import platform diff --git a/cmake/install_codesign_script.cmake b/cmake/install_codesign_script.cmake index 78a671a90f4..8a439cad5d7 100644 --- a/cmake/install_codesign_script.cmake +++ b/cmake/install_codesign_script.cmake @@ -20,7 +20,7 @@ Pre-conditions: This script will codesign the ``FILES_TO_SIGN``, as well as the globbed copied Python .so and the root dylibs (such as ``libintl8.dylib``) -* ``python_standard_lib/lib-dynload/*.so`` +* ``python_lib/lib-dynload/*.so`` * ``lib*.dylib`` To do so, it uses the `CodeSigning`_ functions :cmake:command:`codesign_files_macos` @@ -113,11 +113,11 @@ foreach(path ${_all_root_dylibs}) endif() endforeach() -file(GLOB PYTHON_SOS "${CMAKE_INSTALL_PREFIX}/python_standard_lib/lib-dynload/*.so") +file(GLOB PYTHON_SOS "${CMAKE_INSTALL_PREFIX}/python_lib/lib-dynload/*.so") print_relative_paths(PREFIX "FULL_PATHS=" ABSOLUTE_PATHS ${FULL_PATHS}) print_relative_paths(PREFIX "ROOT_DYLIBS=" ABSOLUTE_PATHS ${ROOT_DYLIBS}) -print_relative_paths(PREFIX "PYTHON_SOS, in ${CMAKE_INSTALL_PREFIX}/python_standard_lib/lib-dynload/=" ABSOLUTE_PATHS ${PYTHON_SOS} NAME_ONLY) +print_relative_paths(PREFIX "PYTHON_SOS, in ${CMAKE_INSTALL_PREFIX}/python_lib/lib-dynload/=" ABSOLUTE_PATHS ${PYTHON_SOS} NAME_ONLY) include(${CMAKE_CURRENT_LIST_DIR}/CodeSigning.cmake) codesign_files_macos( diff --git a/src/EnergyPlus/CMakeLists.txt b/src/EnergyPlus/CMakeLists.txt index 02b39a88543..5a9b3e2fb68 100644 --- a/src/EnergyPlus/CMakeLists.txt +++ b/src/EnergyPlus/CMakeLists.txt @@ -967,9 +967,9 @@ if(LINK_WITH_PYTHON) add_custom_command( TARGET energyplusapi POST_BUILD # TODO: I don't think we want to quote the generator expression - COMMAND ${Python_EXECUTABLE} "${PROJECT_SOURCE_DIR}/cmake/PythonCopyStandardLib.py" "$" "python_standard_lib" + COMMAND ${Python_EXECUTABLE} "${PROJECT_SOURCE_DIR}/cmake/PythonCopyStandardLib.py" "$" "python_lib" COMMAND ${CMAKE_COMMAND} -E env --unset=PIP_REQUIRE_VIRTUALENV - ${Python_EXECUTABLE} -m pip install --target="$/python_standard_lib" --upgrade energyplus-python-apps==24.1a1 + ${Python_EXECUTABLE} -m pip install --target="$/python_lib" --upgrade energyplus-python-apps==24.1a1 ) endif() @@ -978,7 +978,7 @@ if(BUILD_PACKAGE) if(LINK_WITH_PYTHON) # we'll want to grab the standard lib for python plugins # TODO: I don't think we want to quote the generator expression - install(DIRECTORY "$/python_standard_lib/" DESTINATION "./python_standard_lib") + install(DIRECTORY "$/python_lib/" DESTINATION "./python_lib") endif() # we'll want to always provide the C API headers install(FILES ${API_HEADERS} DESTINATION "./include/EnergyPlus/api") diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index dcad9436bab..9d833742a79 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -238,7 +238,7 @@ Built on Platform: {} static constexpr std::array logLevelStrs = {"Trace", "Debug", "Info", "Warn", "Error", "Fatal"}; auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools"); - + auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? enum class ValidAuxiliaryTools { energyplus_iddidf, diff --git a/src/EnergyPlus/PluginManager.cc b/src/EnergyPlus/PluginManager.cc index 3ddd99f71a5..74b8addda9a 100644 --- a/src/EnergyPlus/PluginManager.cc +++ b/src/EnergyPlus/PluginManager.cc @@ -480,7 +480,7 @@ PluginManager::PluginManager(EnergyPlusData &state) : eplusRunningViaPythonAPI(s } else { programDir = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath())); } - fs::path const pathToPythonPackages = programDir / "python_standard_lib"; + fs::path const pathToPythonPackages = programDir / "python_lib"; initPython(state, pathToPythonPackages); @@ -491,7 +491,7 @@ PluginManager::PluginManager(EnergyPlusData &state) : eplusRunningViaPythonAPI(s PyRun_SimpleString("import sys"); // allows us to report sys.path later // we also need to set an extra import path to find some dynamic library loading stuff, again make it relative to the binary - addToPythonPath(state, programDir / "python_standard_lib/lib-dynload", false); + addToPythonPath(state, programDir / "python_lib/lib-dynload", false); // now for additional paths: // we'll always want to add the program executable directory to PATH so that Python can find the installed pyenergyplus package diff --git a/src/EnergyPlus/PythonEngine.cc b/src/EnergyPlus/PythonEngine.cc index 109ea67a1ed..96caf26927f 100644 --- a/src/EnergyPlus/PythonEngine.cc +++ b/src/EnergyPlus/PythonEngine.cc @@ -312,12 +312,12 @@ namespace Python { } else { programDir = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath())); } - fs::path const pathToPythonPackages = programDir / "python_standard_lib"; + fs::path const pathToPythonPackages = programDir / "python_lib"; initPython(state, pathToPythonPackages); // we also need to set an extra import path to find some dynamic library loading stuff, again make it relative to the binary - addToPythonPath(state, programDir / "python_standard_lib/lib-dynload", false); + addToPythonPath(state, programDir / "python_lib/lib-dynload", false); // now for additional paths: // we'll always want to add the program executable directory to PATH so that Python can find the installed pyenergyplus package From be5cff254ea8a5d0d6a33c6951f927d3fe705f89 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Wed, 4 Sep 2024 14:33:19 -0500 Subject: [PATCH 03/14] Trim to just eplaunch for right now --- src/EnergyPlus/CMakeLists.txt | 2 +- src/EnergyPlus/CommandLineInterface.cc | 49 +++++++++++++------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/EnergyPlus/CMakeLists.txt b/src/EnergyPlus/CMakeLists.txt index 5a9b3e2fb68..f2ac7f4d1e7 100644 --- a/src/EnergyPlus/CMakeLists.txt +++ b/src/EnergyPlus/CMakeLists.txt @@ -969,7 +969,7 @@ if(LINK_WITH_PYTHON) POST_BUILD # TODO: I don't think we want to quote the generator expression COMMAND ${Python_EXECUTABLE} "${PROJECT_SOURCE_DIR}/cmake/PythonCopyStandardLib.py" "$" "python_lib" COMMAND ${CMAKE_COMMAND} -E env --unset=PIP_REQUIRE_VIRTUALENV - ${Python_EXECUTABLE} -m pip install --target="$/python_lib" --upgrade energyplus-python-apps==24.1a1 + ${Python_EXECUTABLE} -m pip install --target="$/python_lib" --upgrade energyplus-launch==3.7.2 ) endif() diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index 9d833742a79..e9cf040845c 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -241,36 +241,35 @@ Built on Platform: {} auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? enum class ValidAuxiliaryTools { - energyplus_iddidf, eplaunch }; - std::vector python_fwd_args; - auto *transitionSubcommand = auxiliaryToolsSubcommand->add_subcommand("energyplus_iddidf", "EnergyPlus Python IDD/IDF Utilities"); - transitionSubcommand->add_option("args", python_fwd_args, "Extra Arguments forwarded to the Python script")->option_text("ARG ..."); - transitionSubcommand->positionals_at_end(true); - transitionSubcommand->footer("You can pass extra arguments after the Python file, they will be forwarded."); - - transitionSubcommand->callback([&state, &python_fwd_args] { - EnergyPlus::Python::PythonEngine engine(state); - // There's probably better to be done, like instantiating the pythonEngine with the argc/argv then calling PyRun_SimpleFile but whatever - std::string cmd = R"python(import sys -sys.argv.clear() -sys.argv.append("energyplus") -)python"; - for (const auto &arg : python_fwd_args) { - cmd += fmt::format("sys.argv.append(\"{}\")\n", arg); - } - cmd += R"python( -from energyplus_iddidf.cli import main_cli -main_cli() -)python"; - - engine.exec(cmd); - exit(0); - }); +// auto *transitionSubcommand = auxiliaryToolsSubcommand->add_subcommand("energyplus_iddidf", "EnergyPlus Python IDD/IDF Utilities"); +// transitionSubcommand->add_option("args", python_fwd_args, "Extra Arguments forwarded to the Python script")->option_text("ARG ..."); +// transitionSubcommand->positionals_at_end(true); +// transitionSubcommand->footer("You can pass extra arguments after the Python file, they will be forwarded."); +// +// transitionSubcommand->callback([&state, &python_fwd_args] { +// EnergyPlus::Python::PythonEngine engine(state); +// // There's probably better to be done, like instantiating the pythonEngine with the argc/argv then calling PyRun_SimpleFile but whatever +// std::string cmd = R"python(import sys +// sys.argv.clear() +// sys.argv.append("energyplus") +// )python"; +// for (const auto &arg : python_fwd_args) { +// cmd += fmt::format("sys.argv.append(\"{}\")\n", arg); +// } +// +// cmd += R"python( +// from energyplus_iddidf.cli import main_cli +// main_cli() +// )python"; +// +// engine.exec(cmd); +// exit(0); +// }); auto *epLaunchSubcommand = auxiliaryToolsSubcommand->add_subcommand("eplaunch", "EP-Launch"); epLaunchSubcommand->callback([&state, &python_fwd_args] { From d14fe97be24b07e365510b6b1e15d4a0cd5e76c2 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Wed, 4 Sep 2024 15:41:44 -0500 Subject: [PATCH 04/14] Clang Format --- src/EnergyPlus/CommandLineInterface.cc | 60 +++++++++++++------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index e9cf040845c..fb91da251bb 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -238,38 +238,38 @@ Built on Platform: {} static constexpr std::array logLevelStrs = {"Trace", "Debug", "Info", "Warn", "Error", "Fatal"}; auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools"); - auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? - enum class ValidAuxiliaryTools - { - eplaunch - - }; + auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? + // enum class ValidAuxiliaryTools + // { + // eplaunch + // + // }; std::vector python_fwd_args; -// auto *transitionSubcommand = auxiliaryToolsSubcommand->add_subcommand("energyplus_iddidf", "EnergyPlus Python IDD/IDF Utilities"); -// transitionSubcommand->add_option("args", python_fwd_args, "Extra Arguments forwarded to the Python script")->option_text("ARG ..."); -// transitionSubcommand->positionals_at_end(true); -// transitionSubcommand->footer("You can pass extra arguments after the Python file, they will be forwarded."); -// -// transitionSubcommand->callback([&state, &python_fwd_args] { -// EnergyPlus::Python::PythonEngine engine(state); -// // There's probably better to be done, like instantiating the pythonEngine with the argc/argv then calling PyRun_SimpleFile but whatever -// std::string cmd = R"python(import sys -// sys.argv.clear() -// sys.argv.append("energyplus") -// )python"; -// for (const auto &arg : python_fwd_args) { -// cmd += fmt::format("sys.argv.append(\"{}\")\n", arg); -// } -// -// cmd += R"python( -// from energyplus_iddidf.cli import main_cli -// main_cli() -// )python"; -// -// engine.exec(cmd); -// exit(0); -// }); + // auto *transitionSubcommand = auxiliaryToolsSubcommand->add_subcommand("energyplus_iddidf", "EnergyPlus Python IDD/IDF Utilities"); + // transitionSubcommand->add_option("args", python_fwd_args, "Extra Arguments forwarded to the Python script")->option_text("ARG + // ..."); transitionSubcommand->positionals_at_end(true); transitionSubcommand->footer("You can pass extra arguments after the Python + // file, they will be forwarded."); + // + // transitionSubcommand->callback([&state, &python_fwd_args] { + // EnergyPlus::Python::PythonEngine engine(state); + // // There's probably better to be done, like instantiating the pythonEngine with the argc/argv then calling PyRun_SimpleFile but + // whatever std::string cmd = R"python(import sys + // sys.argv.clear() + // sys.argv.append("energyplus") + // )python"; + // for (const auto &arg : python_fwd_args) { + // cmd += fmt::format("sys.argv.append(\"{}\")\n", arg); + // } + // + // cmd += R"python( + // from energyplus_iddidf.cli import main_cli + // main_cli() + // )python"; + // + // engine.exec(cmd); + // exit(0); + // }); auto *epLaunchSubcommand = auxiliaryToolsSubcommand->add_subcommand("eplaunch", "EP-Launch"); epLaunchSubcommand->callback([&state, &python_fwd_args] { From 5f12ff2b57cd0ea6600af1c3ec77da185e275e5b Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Fri, 6 Sep 2024 08:28:54 -0500 Subject: [PATCH 05/14] Minor cleanup in CLI --- src/EnergyPlus/CommandLineInterface.cc | 50 +------------------------- 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index fb91da251bb..5f0af035b7e 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -235,42 +235,12 @@ Built on Platform: {} app.add_flag("--debug-cli", debugCLI, "Print the result of the CLI assignments to the console and exit")->group(""); // Empty group to hide it #if LINK_WITH_PYTHON - static constexpr std::array logLevelStrs = {"Trace", "Debug", "Info", "Warn", "Error", "Fatal"}; + // static constexpr std::array logLevelStrs = {"Trace", "Debug", "Info", "Warn", "Error", "Fatal"}; auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools"); auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? - // enum class ValidAuxiliaryTools - // { - // eplaunch - // - // }; std::vector python_fwd_args; - // auto *transitionSubcommand = auxiliaryToolsSubcommand->add_subcommand("energyplus_iddidf", "EnergyPlus Python IDD/IDF Utilities"); - // transitionSubcommand->add_option("args", python_fwd_args, "Extra Arguments forwarded to the Python script")->option_text("ARG - // ..."); transitionSubcommand->positionals_at_end(true); transitionSubcommand->footer("You can pass extra arguments after the Python - // file, they will be forwarded."); - // - // transitionSubcommand->callback([&state, &python_fwd_args] { - // EnergyPlus::Python::PythonEngine engine(state); - // // There's probably better to be done, like instantiating the pythonEngine with the argc/argv then calling PyRun_SimpleFile but - // whatever std::string cmd = R"python(import sys - // sys.argv.clear() - // sys.argv.append("energyplus") - // )python"; - // for (const auto &arg : python_fwd_args) { - // cmd += fmt::format("sys.argv.append(\"{}\")\n", arg); - // } - // - // cmd += R"python( - // from energyplus_iddidf.cli import main_cli - // main_cli() - // )python"; - // - // engine.exec(cmd); - // exit(0); - // }); - auto *epLaunchSubcommand = auxiliaryToolsSubcommand->add_subcommand("eplaunch", "EP-Launch"); epLaunchSubcommand->callback([&state, &python_fwd_args] { EnergyPlus::Python::PythonEngine engine(state); @@ -759,33 +729,16 @@ state.dataStrGlobals->inputFilePath='{:g}', // Duplicate the kind of reading the Windows "GetINISetting" would // do. - // REFERENCES: - // na - // Using/Aliasing using namespace EnergyPlus; using namespace DataStringGlobals; - // Locals - // SUBROUTINE ARGUMENT DEFINITIONS: - - // SUBROUTINE PARAMETER DEFINITIONS: - - // INTERFACE BLOCK SPECIFICATIONS - // na - - // DERIVED TYPE DEFINITIONS - // na - - // SUBROUTINE LOCAL VARIABLE DECLARATIONS: - std::string Param; std::string::size_type ILB; std::string::size_type IRB; std::string::size_type IEQ; std::string::size_type IPAR; std::string::size_type IPOS; - std::string::size_type ILEN; // Formats @@ -795,7 +748,6 @@ state.dataStrGlobals->inputFilePath='{:g}', Param = KindofParameter; strip(Param); - ILEN = len(Param); inputFile.rewind(); bool Found = false; bool NewHeading = false; From 61302936402316fcf2c691c67d2d940a36e80a79 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Mon, 9 Sep 2024 11:36:36 -0500 Subject: [PATCH 06/14] Try to create a link to EP Launch via CLI [ci skip] --- cmake/Install.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmake/Install.cmake b/cmake/Install.cmake index e63f1659f52..b01e9fdd91f 100644 --- a/cmake/Install.cmake +++ b/cmake/Install.cmake @@ -494,6 +494,12 @@ if(WIN32) install(PROGRAMS "${PROJECT_SOURCE_DIR}/bin/System/MSINET.OCX" DESTINATION "./temp/" COMPONENT CopyAndRegisterSystemDLLs) install(PROGRAMS "${PROJECT_SOURCE_DIR}/bin/System/Msvcrtd.dll" DESTINATION "./temp/" COMPONENT CopyAndRegisterSystemDLLs) install(PROGRAMS "${PROJECT_SOURCE_DIR}/bin/System/Vsflex7L.ocx" DESTINATION "./temp/" COMPONENT CopyAndRegisterSystemDLLs) + + # On Windows, if we did build with LINK_WITH_PYTHON, we should end up with the new Python Auxiliary CLI for EnergyPlus + # Lets create a link/shortcut to execute EnergyPlus CLI with the right arguments and make launching EP-Launch that much easier + install(CODE "execute_process(COMMAND powershell.exe -ExecutionPolicy Bypass -File ${PROJECT_SOURCE_DIR}/scripts/dev/create_shortcut.ps1 -TargetPath \"energyplus.exe\" -ShortcutPath \"${PROJECT_BINARY_DIR}/EPLaunchPython.lnk\" -Arguments \"auxiliary eplaunch\"") + install(FILES "${PROJECT_BINARY_DIR}/EPLaunchPython.lnk" DESTINATION "./PreProcess/" COMPONENT Auxiliary) + endif() # The group, which will be used to configure the root package From 6c5870573fd70797bf32e80228997cc804cfda85 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Mon, 9 Sep 2024 15:40:36 -0500 Subject: [PATCH 07/14] WIP [ci skip] --- cmake/Install.cmake | 5 ----- cmake/PythonCopyStandardLib.py | 6 ++++++ release/RunEPLaunch.bat | 2 ++ scripts/dev/create_shortcut.ps1 | 11 +++++++++++ src/EnergyPlus/CMakeLists.txt | 6 ++++++ src/EnergyPlus/CommandLineInterface.cc | 26 ++++++++++---------------- 6 files changed, 35 insertions(+), 21 deletions(-) create mode 100644 release/RunEPLaunch.bat create mode 100644 scripts/dev/create_shortcut.ps1 diff --git a/cmake/Install.cmake b/cmake/Install.cmake index b01e9fdd91f..43270dc62d8 100644 --- a/cmake/Install.cmake +++ b/cmake/Install.cmake @@ -495,11 +495,6 @@ if(WIN32) install(PROGRAMS "${PROJECT_SOURCE_DIR}/bin/System/Msvcrtd.dll" DESTINATION "./temp/" COMPONENT CopyAndRegisterSystemDLLs) install(PROGRAMS "${PROJECT_SOURCE_DIR}/bin/System/Vsflex7L.ocx" DESTINATION "./temp/" COMPONENT CopyAndRegisterSystemDLLs) - # On Windows, if we did build with LINK_WITH_PYTHON, we should end up with the new Python Auxiliary CLI for EnergyPlus - # Lets create a link/shortcut to execute EnergyPlus CLI with the right arguments and make launching EP-Launch that much easier - install(CODE "execute_process(COMMAND powershell.exe -ExecutionPolicy Bypass -File ${PROJECT_SOURCE_DIR}/scripts/dev/create_shortcut.ps1 -TargetPath \"energyplus.exe\" -ShortcutPath \"${PROJECT_BINARY_DIR}/EPLaunchPython.lnk\" -Arguments \"auxiliary eplaunch\"") - install(FILES "${PROJECT_BINARY_DIR}/EPLaunchPython.lnk" DESTINATION "./PreProcess/" COMPONENT Auxiliary) - endif() # The group, which will be used to configure the root package diff --git a/cmake/PythonCopyStandardLib.py b/cmake/PythonCopyStandardLib.py index 7fabbef4b47..d55f6c3cce6 100644 --- a/cmake/PythonCopyStandardLib.py +++ b/cmake/PythonCopyStandardLib.py @@ -117,6 +117,12 @@ def find_libs(dir_path): dll_dir = os.path.join(python_root_dir, 'DLLs') shutil.copytree(dll_dir, target_dir, dirs_exist_ok=True) +# And also on Windows, we now need the grab the Tcl/Tk folder that contains config, scripts, and blobs +if platform.system() == 'Windows': + python_root_dir = os.path.dirname(standard_lib_dir) + tcl_dir = os.path.join(python_root_dir, 'tcl') + shutil.copytree(tcl_dir, target_dir, dirs_exist_ok=True) + # then I'm going to try to clean up any __pycache__ folders in the target dir to reduce installer size for root, dirs, _ in os.walk(target_dir): for this_dir in dirs: diff --git a/release/RunEPLaunch.bat b/release/RunEPLaunch.bat new file mode 100644 index 00000000000..5e981f8c89a --- /dev/null +++ b/release/RunEPLaunch.bat @@ -0,0 +1,2 @@ +set PYTHONPATH=%~dp0python_lib +%~dp0python_lib\bin\energyplus_launch.exe diff --git a/scripts/dev/create_shortcut.ps1 b/scripts/dev/create_shortcut.ps1 new file mode 100644 index 00000000000..eefe9b38f61 --- /dev/null +++ b/scripts/dev/create_shortcut.ps1 @@ -0,0 +1,11 @@ +param ( + [string]$TargetPath, + [string]$ShortcutPath, + [string]$Arguments +) + +$WScriptShell = New-Object -ComObject WScript.Shell +$Shortcut = $WScriptShell.CreateShortcut($ShortcutPath) +$Shortcut.TargetPath = $TargetPath +$Shortcut.Arguments = $Arguments +$Shortcut.Save() diff --git a/src/EnergyPlus/CMakeLists.txt b/src/EnergyPlus/CMakeLists.txt index f2ac7f4d1e7..1bb3c1ef519 100644 --- a/src/EnergyPlus/CMakeLists.txt +++ b/src/EnergyPlus/CMakeLists.txt @@ -979,6 +979,12 @@ if(BUILD_PACKAGE) # we'll want to grab the standard lib for python plugins # TODO: I don't think we want to quote the generator expression install(DIRECTORY "$/python_lib/" DESTINATION "./python_lib") + if(WIN32) + # on Windows, with Build Package, and also Link With Python, we can also drop in shortcuts to the new auxiliary CLI + # NOTE: The install command COMPONENTS should line up so that they are run at the same packaging step + install(CODE "execute_process(COMMAND powershell.exe -ExecutionPolicy Bypass -File ${PROJECT_SOURCE_DIR}/scripts/dev/create_shortcut.ps1 -TargetPath \"$\" -ShortcutPath \"$/EPLaunchPython.lnk\" -Arguments \"auxiliary eplaunch\")" COMPONENT Auxiliary) + install(FILES $/EPLaunchPython.lnk DESTINATION "./" COMPONENT Auxiliary) + endif() endif() # we'll want to always provide the C API headers install(FILES ${API_HEADERS} DESTINATION "./include/EnergyPlus/api") diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index 5f0af035b7e..f99eb32c44e 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -243,22 +243,16 @@ Built on Platform: {} auto *epLaunchSubcommand = auxiliaryToolsSubcommand->add_subcommand("eplaunch", "EP-Launch"); epLaunchSubcommand->callback([&state, &python_fwd_args] { - EnergyPlus::Python::PythonEngine engine(state); - // There's probably better to be done, like instantiating the pythonEngine with the argc/argv then calling PyRun_SimpleFile but whatever - std::string cmd = R"python(import sys -sys.argv.clear() -sys.argv.append("energyplus") -)python"; - for (const auto &arg : python_fwd_args) { - cmd += fmt::format("sys.argv.append(\"{}\")\n", arg); - } - - cmd += R"python( -from eplaunch.tk_runner import main_gui -main_gui() -)python"; - - engine.exec(cmd); + fs::path programDir = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath())); + fs::path const pathToPythonPackages = programDir / "python_lib"; + fs::path const tclLibraryDir = pathToPythonPackages / "tcl8.6"; // TODO: Find this dynamically + std::string tclLib = "TCL_LIBRARY=" + tclLibraryDir.string(); + + putenv(tclLib.c_str()); + fs::path const pathToEPLaunchExe = pathToPythonPackages / "bin" / "energyplus_launch.exe"; + system(pathToEPLaunchExe.string().c_str()); + + exit(0); }); From 2fd548f8bbaad9a2f603749fa3613daf10b64ee7 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Fri, 13 Sep 2024 11:58:31 -0500 Subject: [PATCH 08/14] WIP [no ci] --- cmake/Install.cmake | 1 + src/EnergyPlus/CommandLineInterface.cc | 52 +++++++++++++------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/cmake/Install.cmake b/cmake/Install.cmake index 43270dc62d8..709ebfd0ea1 100644 --- a/cmake/Install.cmake +++ b/cmake/Install.cmake @@ -384,6 +384,7 @@ if(WIN32) install(FILES "${PROJECT_SOURCE_DIR}/scripts/Epl-run.bat" DESTINATION "./") install(FILES "${PROJECT_SOURCE_DIR}/scripts/RunDirMulti.bat" DESTINATION "./") install(FILES "${PROJECT_SOURCE_DIR}/release/RunEP.ico" DESTINATION "./") + install(FILES "${PROJECT_SOURCE_DIR}/release/RunEPLaunch.bat" DESTINATION "./") install(FILES "${PROJECT_SOURCE_DIR}/scripts/RunEPlus.bat" DESTINATION "./") install(FILES "${PROJECT_SOURCE_DIR}/scripts/RunReadESO.bat" DESTINATION "./") install(FILES "${PROJECT_SOURCE_DIR}/release/Runep.pif" DESTINATION "./") diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index f99eb32c44e..2d7422a3d6a 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -61,9 +61,9 @@ #include #include -#if LINK_WITH_PYTHON -#include -#endif +// #if LINK_WITH_PYTHON +// #include +// #endif namespace EnergyPlus { @@ -234,29 +234,29 @@ Built on Platform: {} // bool debugCLI = false; app.add_flag("--debug-cli", debugCLI, "Print the result of the CLI assignments to the console and exit")->group(""); // Empty group to hide it -#if LINK_WITH_PYTHON - // static constexpr std::array logLevelStrs = {"Trace", "Debug", "Info", "Warn", "Error", "Fatal"}; - - auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools"); - auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? - std::vector python_fwd_args; - - auto *epLaunchSubcommand = auxiliaryToolsSubcommand->add_subcommand("eplaunch", "EP-Launch"); - epLaunchSubcommand->callback([&state, &python_fwd_args] { - fs::path programDir = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath())); - fs::path const pathToPythonPackages = programDir / "python_lib"; - fs::path const tclLibraryDir = pathToPythonPackages / "tcl8.6"; // TODO: Find this dynamically - std::string tclLib = "TCL_LIBRARY=" + tclLibraryDir.string(); - - putenv(tclLib.c_str()); - fs::path const pathToEPLaunchExe = pathToPythonPackages / "bin" / "energyplus_launch.exe"; - system(pathToEPLaunchExe.string().c_str()); - - - exit(0); - }); - -#endif +// #if LINK_WITH_PYTHON +// // static constexpr std::array logLevelStrs = {"Trace", "Debug", "Info", "Warn", "Error", "Fatal"}; +// +// auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools"); +// auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? +// std::vector python_fwd_args; +// +// auto *epLaunchSubcommand = auxiliaryToolsSubcommand->add_subcommand("eplaunch", "EP-Launch"); +// epLaunchSubcommand->callback([&state, &python_fwd_args] { +// fs::path programDir = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath())); +// fs::path const pathToPythonPackages = programDir / "python_lib"; +// fs::path const tclLibraryDir = pathToPythonPackages / "tcl8.6"; // TODO: Find this dynamically +// std::string tclLib = "TCL_LIBRARY=" + tclLibraryDir.string(); +// +// putenv(tclLib.c_str()); +// fs::path const pathToEPLaunchExe = pathToPythonPackages / "bin" / "energyplus_launch.exe"; +// system(pathToEPLaunchExe.string().c_str()); +// +// +// exit(0); +// }); +// +// #endif app.footer("Example: energyplus -w weather.epw -r input.idf"); From 5bacf1329bbe749cd4280a42bc2918d33e02f3b5 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Mon, 16 Sep 2024 12:16:30 -0500 Subject: [PATCH 09/14] Clang format --- src/EnergyPlus/CommandLineInterface.cc | 46 +++++++++++++------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index 2d7422a3d6a..6ae4bcd9cff 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -234,29 +234,29 @@ Built on Platform: {} // bool debugCLI = false; app.add_flag("--debug-cli", debugCLI, "Print the result of the CLI assignments to the console and exit")->group(""); // Empty group to hide it -// #if LINK_WITH_PYTHON -// // static constexpr std::array logLevelStrs = {"Trace", "Debug", "Info", "Warn", "Error", "Fatal"}; -// -// auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools"); -// auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? -// std::vector python_fwd_args; -// -// auto *epLaunchSubcommand = auxiliaryToolsSubcommand->add_subcommand("eplaunch", "EP-Launch"); -// epLaunchSubcommand->callback([&state, &python_fwd_args] { -// fs::path programDir = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath())); -// fs::path const pathToPythonPackages = programDir / "python_lib"; -// fs::path const tclLibraryDir = pathToPythonPackages / "tcl8.6"; // TODO: Find this dynamically -// std::string tclLib = "TCL_LIBRARY=" + tclLibraryDir.string(); -// -// putenv(tclLib.c_str()); -// fs::path const pathToEPLaunchExe = pathToPythonPackages / "bin" / "energyplus_launch.exe"; -// system(pathToEPLaunchExe.string().c_str()); -// -// -// exit(0); -// }); -// -// #endif + // #if LINK_WITH_PYTHON + // // static constexpr std::array logLevelStrs = {"Trace", "Debug", "Info", "Warn", "Error", "Fatal"}; + // + // auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools"); + // auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? + // std::vector python_fwd_args; + // + // auto *epLaunchSubcommand = auxiliaryToolsSubcommand->add_subcommand("eplaunch", "EP-Launch"); + // epLaunchSubcommand->callback([&state, &python_fwd_args] { + // fs::path programDir = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath())); + // fs::path const pathToPythonPackages = programDir / "python_lib"; + // fs::path const tclLibraryDir = pathToPythonPackages / "tcl8.6"; // TODO: Find this dynamically + // std::string tclLib = "TCL_LIBRARY=" + tclLibraryDir.string(); + // + // putenv(tclLib.c_str()); + // fs::path const pathToEPLaunchExe = pathToPythonPackages / "bin" / "energyplus_launch.exe"; + // system(pathToEPLaunchExe.string().c_str()); + // + // + // exit(0); + // }); + // + // #endif app.footer("Example: energyplus -w weather.epw -r input.idf"); From dafa1b66ba2fabacfa7122488dc7435ae7d70253 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Tue, 17 Sep 2024 09:49:54 -0500 Subject: [PATCH 10/14] Another WIP, looks fine on Linux again for now via CLI [no ci] --- cmake/Install.cmake | 2 - release/RunEPLaunch.bat | 2 - src/EnergyPlus/CommandLineInterface.cc | 59 ++++++++++++++------------ 3 files changed, 33 insertions(+), 30 deletions(-) delete mode 100644 release/RunEPLaunch.bat diff --git a/cmake/Install.cmake b/cmake/Install.cmake index 54aea060079..4315762e629 100644 --- a/cmake/Install.cmake +++ b/cmake/Install.cmake @@ -384,7 +384,6 @@ if(WIN32) install(FILES "${PROJECT_SOURCE_DIR}/scripts/Epl-run.bat" DESTINATION "./") install(FILES "${PROJECT_SOURCE_DIR}/scripts/RunDirMulti.bat" DESTINATION "./") install(FILES "${PROJECT_SOURCE_DIR}/release/RunEP.ico" DESTINATION "./") - install(FILES "${PROJECT_SOURCE_DIR}/release/RunEPLaunch.bat" DESTINATION "./") install(FILES "${PROJECT_SOURCE_DIR}/scripts/RunEPlus.bat" DESTINATION "./") install(FILES "${PROJECT_SOURCE_DIR}/scripts/RunReadESO.bat" DESTINATION "./") install(FILES "${PROJECT_SOURCE_DIR}/release/Runep.pif" DESTINATION "./") @@ -482,7 +481,6 @@ if(WIN32) install(PROGRAMS "${PROJECT_SOURCE_DIR}/bin/System/MSINET.OCX" DESTINATION "./temp/" COMPONENT CopyAndRegisterSystemDLLs) install(PROGRAMS "${PROJECT_SOURCE_DIR}/bin/System/Msvcrtd.dll" DESTINATION "./temp/" COMPONENT CopyAndRegisterSystemDLLs) install(PROGRAMS "${PROJECT_SOURCE_DIR}/bin/System/Vsflex7L.ocx" DESTINATION "./temp/" COMPONENT CopyAndRegisterSystemDLLs) - endif() # The group, which will be used to configure the root package diff --git a/release/RunEPLaunch.bat b/release/RunEPLaunch.bat deleted file mode 100644 index 5e981f8c89a..00000000000 --- a/release/RunEPLaunch.bat +++ /dev/null @@ -1,2 +0,0 @@ -set PYTHONPATH=%~dp0python_lib -%~dp0python_lib\bin\energyplus_launch.exe diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index 6ae4bcd9cff..96e3603892f 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -61,9 +61,9 @@ #include #include -// #if LINK_WITH_PYTHON -// #include -// #endif +#if LINK_WITH_PYTHON +#include +#endif namespace EnergyPlus { @@ -234,29 +234,36 @@ Built on Platform: {} // bool debugCLI = false; app.add_flag("--debug-cli", debugCLI, "Print the result of the CLI assignments to the console and exit")->group(""); // Empty group to hide it - // #if LINK_WITH_PYTHON - // // static constexpr std::array logLevelStrs = {"Trace", "Debug", "Info", "Warn", "Error", "Fatal"}; - // - // auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools"); - // auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? - // std::vector python_fwd_args; - // - // auto *epLaunchSubcommand = auxiliaryToolsSubcommand->add_subcommand("eplaunch", "EP-Launch"); - // epLaunchSubcommand->callback([&state, &python_fwd_args] { - // fs::path programDir = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath())); - // fs::path const pathToPythonPackages = programDir / "python_lib"; - // fs::path const tclLibraryDir = pathToPythonPackages / "tcl8.6"; // TODO: Find this dynamically - // std::string tclLib = "TCL_LIBRARY=" + tclLibraryDir.string(); - // - // putenv(tclLib.c_str()); - // fs::path const pathToEPLaunchExe = pathToPythonPackages / "bin" / "energyplus_launch.exe"; - // system(pathToEPLaunchExe.string().c_str()); - // - // - // exit(0); - // }); - // - // #endif + #if LINK_WITH_PYTHON + auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools"); + auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? + + std::vector python_fwd_args; + auto *epLaunchSubCommand = auxiliaryToolsSubcommand->add_subcommand("eplaunch", "EnergyPlus Launch"); + epLaunchSubCommand->add_option("args", python_fwd_args, "Extra Arguments forwarded to EnergyPlus Launch")->option_text("ARG ..."); + epLaunchSubCommand->positionals_at_end(true); + epLaunchSubCommand->footer("You can pass extra arguments after the eplaunch keyword, they will be forwarded to EnergyPlus Launch."); + + epLaunchSubCommand->callback([&state, &python_fwd_args] { + EnergyPlus::Python::PythonEngine engine(state); + // There's probably better to be done, like instantiating the pythonEngine with the argc/argv then calling PyRun_SimpleFile but whatever + std::string cmd = R"python(import sys +sys.argv.clear() +sys.argv.append("energyplus") +)python"; + for (const auto &arg : python_fwd_args) { + cmd += fmt::format("sys.argv.append(\"{}\")\n", arg); + } + + cmd += R"python( +from eplaunch.tk_runner import main_gui +main_gui() +)python"; + + engine.exec(cmd); + exit(0); + }); + #endif app.footer("Example: energyplus -w weather.epw -r input.idf"); From cbc8547383444bead8e60c2cef9d90afc4a4fb9d Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Tue, 17 Sep 2024 14:28:51 -0500 Subject: [PATCH 11/14] Fixup the TCL and Path stuff to get EPLaunch happy [no ci] --- src/EnergyPlus/CommandLineInterface.cc | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index 96e3603892f..0da01298aa3 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -255,11 +255,30 @@ sys.argv.append("energyplus") cmd += fmt::format("sys.argv.append(\"{}\")\n", arg); } + fs::path programDir = FileSystem::getParentDirectoryPath(FileSystem::getAbsolutePath(FileSystem::getProgramPath())); + fs::path const pathToPythonPackages = programDir / "python_lib"; + std::string sPathToPythonPackages = std::string(pathToPythonPackages.string()); + std::replace(sPathToPythonPackages.begin(), sPathToPythonPackages.end(), '\\', '/'); + cmd += fmt::format("sys.path.insert(0, \"{}\")\n", sPathToPythonPackages); + + std::string tclConfigDir = ""; + for (auto &p : std::filesystem::directory_iterator(pathToPythonPackages)) { + if (p.is_directory()) { + std::string dirName = p.path().filename().string(); + if (dirName.find("tcl", 0) == 0 && dirName.find(".", 0) > 0) { + tclConfigDir = dirName; + break; + } + } + } + cmd += fmt::format("from os import environ\n"); + cmd += fmt::format("environ[\'TCL_LIBRARY\'] = \"{}/{}\"\n", sPathToPythonPackages, tclConfigDir); + cmd += R"python( from eplaunch.tk_runner import main_gui main_gui() )python"; - + std::cout << "Trying to execute this python snippet: " << std::endl << cmd << std::endl; engine.exec(cmd); exit(0); }); From f5fa4bbd4165ccf59e22b0fcf3dd0190b8811e26 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Tue, 17 Sep 2024 14:49:00 -0500 Subject: [PATCH 12/14] Clang format --- src/EnergyPlus/CommandLineInterface.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index 0da01298aa3..a0b1394ac74 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -234,7 +234,7 @@ Built on Platform: {} // bool debugCLI = false; app.add_flag("--debug-cli", debugCLI, "Print the result of the CLI assignments to the console and exit")->group(""); // Empty group to hide it - #if LINK_WITH_PYTHON +#if LINK_WITH_PYTHON auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools"); auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? @@ -282,7 +282,7 @@ main_gui() engine.exec(cmd); exit(0); }); - #endif +#endif app.footer("Example: energyplus -w weather.epw -r input.idf"); From 6fd15903fd70c13e21c3a1ba1bd0ea2e1435ca15 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Wed, 18 Sep 2024 11:25:59 -0500 Subject: [PATCH 13/14] Taking the auxiliary interface away from mac for now --- .github/workflows/release_linux.yml | 2 +- .github/workflows/release_mac.yml | 2 +- .github/workflows/release_windows.yml | 2 +- cmake/PythonCopyStandardLib.py | 5 +++-- cmake/PythonFixUpOnMac.cmake | 17 +++++++++++++++++ src/EnergyPlus/CommandLineInterface.cc | 7 ++++++- 6 files changed, 29 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release_linux.yml b/.github/workflows/release_linux.yml index b99c516016d..44379bb47a6 100644 --- a/.github/workflows/release_linux.yml +++ b/.github/workflows/release_linux.yml @@ -1,4 +1,4 @@ -name: Releases +name: Linux Releases on: push: diff --git a/.github/workflows/release_mac.yml b/.github/workflows/release_mac.yml index 9556e5e893c..6314ca6773c 100644 --- a/.github/workflows/release_mac.yml +++ b/.github/workflows/release_mac.yml @@ -1,4 +1,4 @@ -name: Releases +name: Mac Releases on: push: diff --git a/.github/workflows/release_windows.yml b/.github/workflows/release_windows.yml index 5885ca60967..33611323900 100644 --- a/.github/workflows/release_windows.yml +++ b/.github/workflows/release_windows.yml @@ -1,4 +1,4 @@ -name: Releases +name: Windows Releases on: push: diff --git a/cmake/PythonCopyStandardLib.py b/cmake/PythonCopyStandardLib.py index d55f6c3cce6..fafbcbd3b0e 100644 --- a/cmake/PythonCopyStandardLib.py +++ b/cmake/PythonCopyStandardLib.py @@ -61,7 +61,7 @@ # this script must be called with two args: # 1 - the path to the EnergyPlus executable in the install-tree, which is used to determine where to copy the library # since this is in the install-tree, you'll need to use a cmake generator expression -# 2 - name of the folder to create to store the copied in python standard library, usually python_library +# 2 - name of the folder to create to store the copied in python standard library, usually python_lib import ctypes import os import platform @@ -122,7 +122,8 @@ def find_libs(dir_path): python_root_dir = os.path.dirname(standard_lib_dir) tcl_dir = os.path.join(python_root_dir, 'tcl') shutil.copytree(tcl_dir, target_dir, dirs_exist_ok=True) - +# TODO: Need to do this on Mac as well, see the bottom of cmake/PythonFixUpOnMac.cmake for more info + # then I'm going to try to clean up any __pycache__ folders in the target dir to reduce installer size for root, dirs, _ in os.walk(target_dir): for this_dir in dirs: diff --git a/cmake/PythonFixUpOnMac.cmake b/cmake/PythonFixUpOnMac.cmake index 615d04340a3..21c2410dc69 100644 --- a/cmake/PythonFixUpOnMac.cmake +++ b/cmake/PythonFixUpOnMac.cmake @@ -71,3 +71,20 @@ foreach(PREREQ IN LISTS PREREQUISITES) execute_process(COMMAND "install_name_tool" -change "${PREREQ}" "@loader_path/${LIB_INT_FILENAME}" "${LOCAL_PYTHON_LIBRARY}") endif() endforeach() + +##############3 +# we need to look into the tkinter binary's runtime dependencies, copy over the tcl and tk dylibs, update them with install_name_tool, and make sure they all get signed, like this: + +# libtcl +# cp /opt/homebrew/opt/tcl-tk/lib/libtcl8.6.dylib /path/to/python_lib/lib-dynload/ +# install_name_tool -change "/opt/homebrew/opt/tcl-tk/lib/libtcl8.6.dylib" "@loader_path/libtcl8.6.dylib" /path/to/python_lib/lib-dynload/_tkinter.cpython-312-darwin.so + +# Do the same for libtk +# cp /opt/homebrew/opt/tcl-tk/lib/libtk8.6.dylib /path/to/python_lib/lib-dynload/ +# install_name_tool -change "/opt/homebrew/opt/tcl-tk/lib/libtk8.6.dylib" "@loader_path/libtk8.6.dylib" /path/to/python_lib/lib-dynload/_tkinter.cpython-312-darwin.so + +# Resign _tkinter +# codesign -vvvv -s "Developer ID Application: National ..." -f --timestamp -i "org.nrel.EnergyPlus" -o runtime /path/to/python_lib/lib-dynload/_tkinter.cpython-312-darwin.so +# codesign -vvvv -s "Developer ID Application: National ..." -f --timestamp -i "org.nrel.EnergyPlus" -o runtime /path/to/python_lib/lib-dynload/libtcl8.6.dylib +# codesign -vvvv -s "Developer ID Application: National ..." -f --timestamp -i "org.nrel.EnergyPlus" -o runtime /path/to/python_lib/lib-dynload/libtk8.6.dylib +##############3 diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index a0b1394ac74..c4d563eb07a 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -235,6 +235,10 @@ Built on Platform: {} app.add_flag("--debug-cli", debugCLI, "Print the result of the CLI assignments to the console and exit")->group(""); // Empty group to hide it #if LINK_WITH_PYTHON +#if __APPLE__ + // for now on Apple we are not providing the command line interface to EP-Launch due to packaging issues + // once that is fixed, this __APPLE__ block will be removed and we'll just have this on all platforms +#else auto *auxiliaryToolsSubcommand = app.add_subcommand("auxiliary", "Run Auxiliary Python Tools"); auxiliaryToolsSubcommand->require_subcommand(); // should default to requiring 1 or more additional args? @@ -278,10 +282,11 @@ sys.argv.append("energyplus") from eplaunch.tk_runner import main_gui main_gui() )python"; - std::cout << "Trying to execute this python snippet: " << std::endl << cmd << std::endl; + // std::cout << "Trying to execute this python snippet: " << std::endl << cmd << std::endl; engine.exec(cmd); exit(0); }); +#endif #endif app.footer("Example: energyplus -w weather.epw -r input.idf"); From d52ea16520d4014a7a0f1deb053a18eefbcfafc1 Mon Sep 17 00:00:00 2001 From: Edwin Lee Date: Wed, 18 Sep 2024 11:27:00 -0500 Subject: [PATCH 14/14] Remove unneeded format call --- src/EnergyPlus/CommandLineInterface.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EnergyPlus/CommandLineInterface.cc b/src/EnergyPlus/CommandLineInterface.cc index c4d563eb07a..71b95a39d40 100644 --- a/src/EnergyPlus/CommandLineInterface.cc +++ b/src/EnergyPlus/CommandLineInterface.cc @@ -275,7 +275,7 @@ sys.argv.append("energyplus") } } } - cmd += fmt::format("from os import environ\n"); + cmd += "from os import environ\n"; cmd += fmt::format("environ[\'TCL_LIBRARY\'] = \"{}/{}\"\n", sPathToPythonPackages, tclConfigDir); cmd += R"python(