Skip to content

Conversation

@Myoldmopar
Copy link
Member

Pull request overview

Allows launching the Python based version updater tool from the E+ CLI:

Screencast.from.2025-03-04.12-14-56.mp4

I'll kick off a test package so that any interested party could try it out. As of now I've only exercised the E+ CLI on Linux, but it works the same as the EP Launch CLI, which is happy on all platforms. Also, I had to make some improvements to the updater tool, and it's tested heavily across platforms itself, so I think it should be OK.....

@Myoldmopar Myoldmopar added the NewFeature Includes code to add a new feature to EnergyPlus label Mar 4, 2025
@Myoldmopar Myoldmopar requested review from jmarrec and mjwitte March 4, 2025 18:19
@Myoldmopar Myoldmopar self-assigned this Mar 4, 2025
Copy link
Member Author

@Myoldmopar Myoldmopar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note - this will still execute the underlying Fortran based transition binaries. It just launches the Python GUI. IDFVersionUpdater is I think broken again on Ubuntu 24.04, so this would eliminate the need for that. I am open to removing it entirely in favor of this.

POST_BUILD # TODO: I don't think we want to quote the generator expression
COMMAND ${CMAKE_COMMAND} -E env --unset=PIP_REQUIRE_VIRTUALENV
${Python_EXECUTABLE} -m pip install --target="$<TARGET_FILE_DIR:energyplusapi>/python_lib" --upgrade energyplus-launch==3.7.2
${Python_EXECUTABLE} -m pip install --target="$<TARGET_FILE_DIR:energyplusapi>/python_lib" --upgrade energyplus-launch==3.7.2 energyplus-transition-tools==2.1.1
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just grab the transition tool package, at version 2.1.1 which I just released this morning after some cleanups.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When do we start using https://github.com/Myoldmopar/EnergyPlusPythonApps/ as a meta package itself?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


cmd += R"python(
from energyplus_transition.runner import main_gui
main_gui(True)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the auxiliary updater keywords are passed to the E+ CLI, it will launch the transition GUI and leave. This could be modified later to like pass the IDF file in, but I truly don't think that's necessary. This is launching a GUI, no one should be integrated this auxiliary argument into some sort of automated workflow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And yes, there is quite a bit of duplicate from the EP Launch code right above. Would be nice to collect things into the PythonEngine class and out of here. But doesn't have to be right now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the auxiliary updater keywords are passed to the E+ CLI, it will launch the transition GUI and leave. This could be modified later to like pass the IDF file in, but I truly don't think that's necessary. This is launching a GUI, no one should be integrated this auxiliary argument into some sort of automated workflow.

It'd sure be nice if one could bypass the GUI and do something like energyplus auxiliary updater myfile.idf (potentially with a --up-to-version 24.2.0) to directly transition the file in the command line.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would be great, let's do that soon.

@mjwitte
Copy link
Contributor

mjwitte commented Mar 4, 2025

Tested this on Windows.

  1. Transitioned a v9.1 file up to 25.1, worked as expected, except the new files are in the "run directory" which is the \PreProcess\IDFVersionUpdater folder. Can we have an option to save the files in the location of the original idf?
  2. After launching the GUI, the command prompt doesn't return until the GUI is closed. Is that the expected behavior?
    image
  3. The current IDFVersionUpdater has an option to process a list of files.
  4. Can we add a double-clickable shortcut to launch this from a folder (so I don't have to remember the syntax to type)
  5. And save the transition audit output.

@Myoldmopar
Copy link
Member Author

@mjwitte Thank you!! OK, got nearly everything resolved for you.

  1. Added a menu option that defaults to True which will copy the final version of the transitioned file back to the original input file dir
  2. Yes, that's fine
  3. I added in the ability to do lst files. It's a bit hacky, but seems to work really well. Would love your testing.
  4. Yes, added a shortcut
  5. I did not do this, but am open to it. I just can't remember what all files I need to grab.

I am going to kick off another test package with all this in it. Let me know if you find anything else!

@jmarrec
Copy link
Contributor

jmarrec commented Mar 5, 2025

Added a menu option that defaults to True which will copy the final version of the transitioned file back to the original input file dir

That makes me puzzled. There is no reason it would not be in the original dir, is there? If I run this

transition_exe = Path('/path/to/EnergyPlus/PreProcess/IDFVersionUpdater/Transition-VXXX-to-VYYY')
subprocess.check_call([str(transition_exe), '/full/path/to/file.idf'], cwd=transition_exe.parent)

Then the idf object is transition at its original location to begin with

# 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 \"$<TARGET_FILE:energyplus>\" -ShortcutPath \"$<TARGET_FILE_DIR:energyplus>/EPLaunchPython.lnk\" -Arguments \"auxiliary eplaunch\")" COMPONENT Auxiliary)
install(CODE "execute_process(COMMAND powershell.exe -ExecutionPolicy Bypass -File ${PROJECT_SOURCE_DIR}/scripts/dev/create_shortcut.ps1 -TargetPath \"$<TARGET_FILE:energyplus>\" -ShortcutPath \"$<TARGET_FILE_DIR:energyplus>/IDFVersionUpdater.lnk\" -Arguments \"auxiliary updater\")" COMPONENT Auxiliary)
install(FILES $<TARGET_FILE_DIR:energyplus>/EPLaunchPython.lnk DESTINATION "./" COMPONENT Auxiliary)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IDFVersionUpdater shortcut is not there. Does it need a line like this one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sheesh. Yes. I've got another round of tweaks on the updater tool itself that I'm wrapping up and will tag/pin a new version, and update this here.

@mjwitte
Copy link
Contributor

mjwitte commented Mar 5, 2025

@mjwitte Thank you!! OK, got nearly everything resolved for you.

Thanks @Myoldmopar !

  1. Added a menu option that defaults to True which will copy the final version of the transitioned file back to the original input file dir

Getting there.
Input directory:
image

Would prefer to see here:
RefBldgHospitalNew2004_Chicago.idf (as the new updated file with today's date so I can run it now)
RefBldgHospitalNew2004_Chicago_24.1.idf (the original file renamed)
RefBldgHospitalNew2004_Chicago_24.2.idf (intermediate file)

Run Directory
image

Need to cleanup all of these. VCPerr file is the audit from the first conversion, not sure where the other audit output is for the later conversions?

  1. Yes, that's fine
  2. I added in the ability to do lst files. It's a bit hacky, but seems to work really well. Would love your testing.
    Tested with a short list of files, some with different starting versions. Looks good.
  1. Yes, added a shortcut
  2. I did not do this, but am open to it. I just can't remember what all files I need to grab.

I am going to kick off another test package with all this in it. Let me know if you find anything else!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'll plan on just naming this info.plist.in since it becomes info.plist.

@Myoldmopar
Copy link
Member Author

So there is a ValueError saying that launcher.sh isn't signed. The launcher.sh is a minimal little bash script that goes inside the newly generated .app bundles for EPLaunch and VersionUpdater. I am not sure that those need to be signed, but I found the spot where other bash scripts (runenergyplus) were being signed, so I added it to that list. I'm not 100% sure how declaring the name there is enough, but it seems to work for runenergyplus and the others. Anxious to learn about how to address this properly since there will be more coming later.

@jmarrec FYI and HELP! 😆

OK, so the install(signer script could not find the launcher.sh file. It looks like it is trying to find it in the packages/Unspecified/data folder.  But I had used COMPONENT Auxiliary in the install( for the app bundle.  I am kinda hopeful that maybe this will work.  By not specifying a COMPONENT, it will end up in the Unspecified package to match the signer script.  Let's see.
});

auto *updaterSubCommand = auxiliaryToolsSubcommand->add_subcommand("updater", "IDF Version Updater");
updaterSubCommand->add_option("args", python_fwd_args, "Extra Arguments forwarded to IDF Version Updater")->option_text("ARG ...");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What extra args does this accept? I'm confused.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't...for now. This could be considered future proofing and not hurt anything right now.


cmd += R"python(
from energyplus_transition.runner import main_gui
main_gui(True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the auxiliary updater keywords are passed to the E+ CLI, it will launch the transition GUI and leave. This could be modified later to like pass the IDF file in, but I truly don't think that's necessary. This is launching a GUI, no one should be integrated this auxiliary argument into some sort of automated workflow.

It'd sure be nice if one could bypass the GUI and do something like energyplus auxiliary updater myfile.idf (potentially with a --up-to-version 24.2.0) to directly transition the file in the command line.

"runenergyplus"
"runepmacro"
"runreadvars"
"launcher.sh"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to sign the .app bundle itself.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think bash scripts really need to be signed?

In any case, your launcher.sh If I'm reading this PR correctly does NOT live at INSTALL_PATH/launcher.sh but at INSTALL_PATH/AppName.app/Contents/MacOS/launcher.sh, right?

@Myoldmopar Myoldmopar added this to the EnergyPlus 25.1 milestone Mar 17, 2025
@Myoldmopar
Copy link
Member Author

This is super close now. I'm glad CI is all clean, and all the package builds were successful. What I see now:

  • On Windows, the start menu shortcut was one long quoted string instead of a quoted executable and argument, so I had to tweak the QtIFW to handle that better. Should be good to go.
  • On Windows, I also build some clickable shortcuts in the E+ install root called EP-LaunchPython and IDFVersionUpdater. They weren't behaving, so they are fixed now.
  • Outside of maybe some renaming, and documentation, Windows should be good to go.
  • On Linux, I'm still not able to get it to launch without setting LD_LIBRARY_PATH from outside the call to EnergyPlus. Even though I'm setting it inside E+ as I make the call to the Python interpreter. I added a debug message to help diagnose, and will kick off test 9.

Name=EnergyPlus ENERGYPLUS_VERSION
Comment=Run EnergyPlus Launch
Exec=ENERGYPLUS_INSTALL_DIR/energyplus auxiliary eplaunch
Exec=env LD_LIBRARY_PATH=ENERGYPLUS_INSTALL_DIR/python_lib/lib-dynload ENERGYPLUS_INSTALL_DIR/energyplus auxiliary eplaunch
Copy link
Contributor

@jmarrec jmarrec Mar 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're perhaps going about it the wrong way.

First, on mac, what we do is to:

  • Copy the tcl libs
  • Use install_name_tool to change the _tkinter.so so look for these libs and NOT at /opt/homebrew/xxxx . See cmake/PythonFixUpTclTk.py
  • That way no need to mess with the LD_LIBRARY_PATH or anything

So at the very least, you should probably do the same with Linux.

(py312)(3.2.2)julien@EnergyPlus-build-release$ ldd Products/python_lib/lib-dynload/_tkinter.cpython-312-x86_64-linux-gnu.so
	linux-vdso.so.1 (0x0000734351f28000)
+	libtk8.6.so => /lib/x86_64-linux-gnu/libtk8.6.so (0x0000734351d84000)
+	libtcl8.6.so => /lib/x86_64-linux-gnu/libtcl8.6.so (0x0000734351bd7000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x0000734351800000)
	libXft.so.2 => /lib/x86_64-linux-gnu/libXft.so.2 (0x0000734351bbc000)
	libfontconfig.so.1 => /lib/x86_64-linux-gnu/libfontconfig.so.1 (0x0000734351b6b000)
-	libX11.so.6 => /lib/x86_64-linux-gnu/libX11.so.6 (0x0000734351a2c000)

But Honestly, I don't see why we should ship libtk/libtcl any more than libX11 for eg.

On mac, there is a difference:

  1. You must have brew installed to brew install tcl-tk . Ubuntu users have apt already.
  2. The newest brew recipe is incompatible, you have to specifically call brew install tcl-tk@8

FWIW, in OpenStudio when we build the .deb installer, we do set the dependencies that are missing in E+:

https://github.com/NREL/OpenStudio/blob/98207d21649408d964fbc2660110a4cbdb35eb7d/CMakeLists.txt#L1264

set(CPACK_DEBIAN_PACKAGE_DEPENDS "libgomp1, libx11-6") # This is for EnergyPlus

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tk_so = locate_tk_so(python_dir)
tcl_tk_sos = [Path(t["libname"]) for t in get_linked_libraries(tk_so) if "libt" in t["libname"]]
any_missing = False
for tcl_tk_so in tcl_tk_sos:
new_tcl_tk_so = lib_dynload_dir / tcl_tk_so.name
if str(tcl_tk_so).startswith('@loader_path'):
assert new_tcl_tk_so.is_file(), f"{new_tcl_tk_so} missing when the tkinter so is already adjusted. Wipe the dir"
print("Already fixed up the libtcl and libtk, nothing to do here")
continue
elif not str(tcl_tk_so).startswith('@') and not tcl_tk_so.exists():
print(f"Hmmm... could not find the dependency shared object at {tcl_tk_so}; script will continue but fail")
any_missing = True
continue
shutil.copy(tcl_tk_so, new_tcl_tk_so)
# during testing, the brew installed tcl and tk libraries were installed without write permission
# the workaround was to manually chmod u+w those files in the brew install folder
# instead let's just fix them up once we copy them here
current_perms = os.stat(str(new_tcl_tk_so)).st_mode
os.chmod(str(new_tcl_tk_so), current_perms | stat.S_IWUSR)
# now that it can definitely be written, we can run install_name_tool on it
subprocess.check_output(
["install_name_tool", "-change", str(tcl_tk_so), f"@loader_path/{new_tcl_tk_so.name}", str(tk_so)]
)
# Change the id that's the first line of the otool -L in this case and it's confusing
subprocess.check_output(["install_name_tool", "-id", str(new_tcl_tk_so.name), str(new_tcl_tk_so)])

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of my main goals is to continue to allow a non-sudo install of EnergyPlus on Linux. If the user doesn't want system symlinks, and wants to install it in a user-owned folder, they shouldn't need to follow up with sudo installing stuff. I am not going to die on that hill, but I will risk being injured on it. I have it working really well, so I think we should put 25.1 out there with this, and we can make a correction if needed as we go into the next version.

Copy link
Member Author

@Myoldmopar Myoldmopar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still a few things left to consider, but walkthrough is done.

  • Icons labeled and version numbered?
    • This isn't happening now. I want to get a version of E+ out the door, and get the full experience across all platforms, and then make improvements based on that. At a minimum, I suspect we'll end up making some new icons for EPLaunch vs VersionUpdater. And we may need to tweak the Python code for those to get the icons to behave really nicely. So this is for later.
  • add_custom_commands all need to identify their output files
    • I would still love this, but it needs a little work. The BYPRODUCTS cannot be target-specific generators, and we do a lot of target-specific stuff in the add_custom_commands related to Python. I definitely want to rethink that stuff but that's not today.
  • .desktop should be part of uninstall
  • patchelf instead of LD_LIBRARY_PATH
  • remove IDFVersionUpdater classic? At least from Linux?
  • Update documentation
  • Move IDFVersionUpdater shortcut into preprocess?
    • No, but I put a "VersionUpdater-NEWS.txt file in the PreProcess folder to help users find out about the new stuff
  • Remove the auxiliary.sh.in stuff on Linux, is it used?
    • For now I'm going to leave it because it allows a user to double click to launch the tools from the install folder. If we can get the LD_LIBRARY_PATH thing solved, then it may not be as needed. I will double check that we are creating similar names of these shortcuts in all platforms though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, so I'll do a walkthrough now. There are still a couple things left but it's very very close.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to @jmarrec for getting these Python app bundles signed and packed up nicely. Things are working amazingly on Mac. I imagine we will want more of these as we add a few more apps (EpJSONEditor...)


if (PYTHON_CLI)
cpack_ifw_configure_component(Unspecified SCRIPT cmake/qtifw/install_auxiliary_python_shortcuts.qs)
endif()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, so here is a new QtIFW script that is attached to the Unspecified component. Could consider adding it to the Auxiliary or something if we wanted all the Python CLI app stuff in Auxiliary.

Anyway, this script will handle any desktop integration steps to enhance the user experience.

if __name__ == "__main__":

if platform.system() != "Darwin":
if platform.system() != "Darwin" and platform.system() != 'Linux':
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we are packaging up the tcl and tk stuff for Ubuntu, we need to abort if we are not on one of those (so .... Windows 🙃 )


if any_missing:
sys.exit(1)
if platform.system() == 'Darwin':
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are on Mac, we haven't changed anything from 24.2. We still find the tk blobs, move things around, and run install_name_tool to get everyone happy.

sys.exit(1)

elif platform.system() == 'Linux':
# On Linux, we need to copy the libtk.X.Y.so file from usually /usr/lib/x86_64-linux-gnu
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Linux, we are currently getting the libtk library, plus the tcl and tk config/data directories and bringing them along. As it stands right now, on Linux you need to tell the process where to find libtk8.6.so in order to run the Python GUIs. This is hidden from the user through the desktop integration and setting LD_LIBRARY_PATH. But @jmarrec feels we should do a patchelf to fix up the _tkinter library instead. I'm on board, and this will likely be fixed up before merge. I will say, it does seem like everything is running happily though.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed the patchelf stuff worked! Note that when you apt install python3-tk, you will get a _tkinter shared object library that is stripped of it's ELF section headers, and you cannot write an rpath into it. A warning has been added to GHA if it encounters this condition.

install(
FILES ${PROJECT_SOURCE_DIR}/cmake/energyplus.desktop.in
DESTINATION share/applications
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Linux we now need to create a couple shell scripts to launch EPLaunch and VersionUpdater right in the install root. On second thought, maybe these aren't needed? I'll consider removing them.

And we also now install a .desktop template inside install root / share/applications to be customized at install time by the QtIFW scripts.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to leave these little shortcuts in. I honestly doubt many people will ever use them on Linux. Either they will be happy on a terminal and just execute ./energyplus auxiliary eplaunch, or they will use the desktop integration and click on the E+ icon on the dock. But it's fine for now.

});

auto *updaterSubCommand = auxiliaryToolsSubcommand->add_subcommand("updater", "IDF Version Updater");
updaterSubCommand->add_option("args", python_fwd_args, "Extra Arguments forwarded to IDF Version Updater")->option_text("ARG ...");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't...for now. This could be considered future proofing and not hurt anything right now.


cmd += R"python(
from energyplus_transition.runner import main_gui
main_gui(True)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that would be great, let's do that soon.

cmd += fmt::format("environ[\'TCL_LIBRARY\'] = \"{}/{}\"\n", sPathToPythonPackages, tclConfigDir);
cmd += fmt::format("environ[\'TK_LIBRARY\'] = \"{}/{}\"\n", sPathToPythonPackages, tkConfigDir);
return cmd;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was duplicating this for each GUI, so I made a worker function out of it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the worker function on Windows that can execute a script without opening a terminal. The shenanigans are at a all time high, but this works so perfectly. I'm quite happy with it.

Comment on lines +60 to +68
// Remove the executable name to get the directory
std::wstring directory(path);
size_t pos = directory.find_last_of(L"\\/");
if (pos != std::wstring::npos) {
directory = directory.substr(0, pos); // Extract just the directory path
}

// Construct the target application path (assuming it's in the same directory)
std::wstring targetApp = directory + L"\\energyplus.exe";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One could argue you should be using std::filesystem::path (which uses wchar_t on windows), instead of string manipulation. But you're already using Windows API, so screwed for screwed 🤷

https://godbolt.org/z/G1Ydo6qEz

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could call GetLastError to format the system error message. Untested:

diff --git a/src/EnergyPlus/WindowsGuiLauncher.cc b/src/EnergyPlus/WindowsGuiLauncher.cc
index 8d63d8b3f1..b8a007275a 100644
--- a/src/EnergyPlus/WindowsGuiLauncher.cc
+++ b/src/EnergyPlus/WindowsGuiLauncher.cc
@@ -45,30 +45,53 @@
 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 // POSSIBILITY OF SUCH DAMAGE.
 
+#include <filesystem>
 #include <string>
 #include <windows.h>
 
+void ErrorExit()
+{
+    // Retrieve the system error message for the last-error code
+
+    LPVOID lpMsgBuf;
+    DWORD dw = GetLastError();
+
+    if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+                       NULL,
+                       dw,
+                       MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+                       (LPWSTR)&lpMsgBuf,
+                       0,
+                       NULL) == 0) {
+        MessageBoxW(NULL, TEXT("FormatMessage failed"), TEXT("Error"), MB_OK);
+        ExitProcess(dw);
+    }
+
+    MessageBoxW(NULL, (LPWSTR)lpMsgBuf, TEXT("Error"), MB_ICONERROR | MB_OK);
+
+    LocalFree(lpMsgBuf);
+    ExitProcess(dw);
+}
+
 int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
 {
+    using namespace fs = std::filesystem;
+
     // Get the path of the current executable
-    wchar_t path[MAX_PATH];
-    if (GetModuleFileNameW(NULL, path, MAX_PATH) == 0) {
+    wchar_t c_path[MAX_PATH];
+    if (GetModuleFileNameW(NULL, c_path, MAX_PATH) == 0) {
         MessageBoxW(NULL, L"Failed to get module file name!", L"Error", MB_ICONERROR | MB_OK);
         return 1;
     }
 
     // Remove the executable name to get the directory
-    std::wstring directory(path);
-    size_t pos = directory.find_last_of(L"\\/");
-    if (pos != std::wstring::npos) {
-        directory = directory.substr(0, pos); // Extract just the directory path
-    }
+    fs::path thisExePath(c_path);
+    fs::path thisDirPath = thisExePath.parent_path();
 
     // Construct the target application path (assuming it's in the same directory)
-    std::wstring targetApp = directory + L"\\energyplus.exe";
+    fs::path targetApp = thisDirPath / L"energyplus.exe";
 
-    // Check if the file exists
-    if (GetFileAttributesW(targetApp.c_str()) == INVALID_FILE_ATTRIBUTES) {
+    if (!fs::is_regular_file(targetApp)) {
         MessageBoxW(NULL, L"Application not found!", L"Error", MB_ICONERROR | MB_OK);
         return 1;
     }
@@ -78,19 +101,19 @@ int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int
     args += lpCmdLine;
 
     // Launch the target application
-    HINSTANCE hInstance2 = ShellExecuteW(NULL,              // Parent window
-                                         L"open",           // Operation
-                                         targetApp.c_str(), // Executable path
-                                         args.c_str(),      // Arguments
-                                         directory.c_str(), // Working directory
-                                         SW_HIDE            // Hide console
+    HINSTANCE hInstance2 = ShellExecuteW(NULL,                // Parent window
+                                         L"open",             // Operation
+                                         targetApp.c_str(),   // Executable path
+                                         args.c_str(),        // Arguments
+                                         thisDirPath.c_str(), // Working directory
+                                         SW_HIDE              // Hide console
     );
 
-    // Error handling
+    // Error handling: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew#return-value
     if ((intptr_t)hInstance2 <= 32) {
         std::wstring errorMsg = L"Failed to launch application! Error code: " + std::to_wstring((intptr_t)hInstance2);
         MessageBoxW(NULL, errorMsg.c_str(), L"Error", MB_ICONERROR | MB_OK);
-        return 1;
+        ErrorExit();
     }
 
     return 0;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferring this until later, but very happy to get this tested and integrated later.

@Myoldmopar
Copy link
Member Author

And it is totally happy on Linux! I just need to know from @jmarrec if those Windows changes are worth pulling in or not. If not, then this could be ready to merge in. I think now I'll just defer any documentation changes until my very last PrepFor251 style branch where I update the README and all the other silly stuff.

@Myoldmopar
Copy link
Member Author

OK, and it seems that the Windows changes are not important right now. In the spirit of trying to get this in the hands of testers as soon as possible, I'm planning on merging this once CI finishes up, and tagging RC1. I will await testing results and start modifying documentation for these changes in the meantime.

Copy link
Member Author

@Myoldmopar Myoldmopar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, that's it. Code walkthrough is done. Merging this in. If CI doesn't find anything in the normal develop testing, then I'll tag it as RC1 and start working on docs/release notes.

echo "Using Apt to install dependencies"
sudo apt-get update
sudo apt-get install texlive texlive-xetex texlive-science libxkbcommon-x11-0 xorg-dev libgl1-mesa-dev
sudo apt-get install texlive texlive-xetex texlive-science libxkbcommon-x11-0 xorg-dev libgl1-mesa-dev patchelf
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Patchelf is used to edit the ELF header in the _tkinter shared object library on Linux, similar to install_name_tool on Mac.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the IDFVersionUpdater for Linux....Windows and Mac are going to be gone with the next release.

sys.exit(1)

elif platform.system() == 'Linux':
# On Linux, we need to copy the libtk.X.Y.so file from usually /usr/lib/x86_64-linux-gnu
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed the patchelf stuff worked! Note that when you apt install python3-tk, you will get a _tkinter shared object library that is stripped of it's ELF section headers, and you cannot write an rpath into it. A warning has been added to GHA if it encounters this condition.


# informative messaging
message("Setting up Python API, creating pyenergyplus package at ${EPLUS_EXE_DIR}/pyenergyplus")
message("PYTHON: Setting up API, creating pyenergyplus package at ${EPLUS_EXE_DIR}/pyenergyplus")
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to move these to regular CMake messages instead of print statements.


[Desktop Action VersionUpdater]
Name=Open IDF Version Updater
Exec=ENERGYPLUS_INSTALL_DIR/energyplus auxiliary updater
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLEAN little desktop file. :)

"else echo 'Error: .desktop template file missing'; fi"
],
"UNDOEXECUTE",
"rm", desktopFilePath
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even added an uninstall step to clean up the .desktop file path.

*Linux note*: if you try to launch the VersionUpdater program from the default files app, it
may launch in a text editor (since it is a shell script). Just right click and ask to "Run as a Program".

The classic version updater is included with this version on Windows and Mac, but will be removed in the future.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users will find this file in the PreProcess folder if they go hunting for IDFVersionUpdater. This is just one vector that I'll be using to alert the users of these changes.

install(
FILES ${PROJECT_SOURCE_DIR}/cmake/energyplus.desktop.in
DESTINATION share/applications
)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to leave these little shortcuts in. I honestly doubt many people will ever use them on Linux. Either they will be happy on a terminal and just execute ./energyplus auxiliary eplaunch, or they will use the desktop integration and click on the E+ icon on the dock. But it's fine for now.

Comment on lines +60 to +68
// Remove the executable name to get the directory
std::wstring directory(path);
size_t pos = directory.find_last_of(L"\\/");
if (pos != std::wstring::npos) {
directory = directory.substr(0, pos); // Extract just the directory path
}

// Construct the target application path (assuming it's in the same directory)
std::wstring targetApp = directory + L"\\energyplus.exe";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferring this until later, but very happy to get this tested and integrated later.

@Myoldmopar Myoldmopar merged commit 9d11d2b into develop Mar 20, 2025
24 checks passed
@Myoldmopar Myoldmopar deleted the PackageIDFUpdater branch March 20, 2025 18:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

NewFeature Includes code to add a new feature to EnergyPlus

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants