Skip to content

Commit

Permalink
Generate a batch script on Windows (launches exe) (#8572)
Browse files Browse the repository at this point in the history
* Generate a batch script on Windows (launches exe)

  - We now generate a <ModelName>.bat file along with other generated files.

    The script updates the PATH with the directories extracted from the model
    and then launches the executable.

  - The APIs `simulate`, `optimize` and `linearize` now launch executables
    using this bat file.

  - OMEdit manually parses the bat file, extracts the additional paths
    needed and updates the PATH itself before launching the executable.

  - OMNotebook and OMShell have not been updated yet.

* Catch the return code of the executable and exit with it.

  - To be safe catch the %ERRORLEVEL% of the executable call and exit
    with that.
  • Loading branch information
mahge committed Feb 18, 2022
1 parent 00da4a9 commit 56e1f47
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 72 deletions.
75 changes: 14 additions & 61 deletions OMCompiler/Compiler/Script/CevalScriptBackend.mo
Expand Up @@ -1548,7 +1548,7 @@ algorithm
System.realtimeTick(ClockIndexes.RT_CLOCK_SIMULATE_SIMULATION);
SimulationResults.close() "Windows cannot handle reading and writing to the same file from different processes like any real OS :(";

resI := setPathsAndSystemCall(sim_call,dirs,logFile);
resI := System.systemCall(sim_call, logFile);

timeSimulation := System.realtimeTock(ClockIndexes.RT_CLOCK_SIMULATE_SIMULATION);

Expand Down Expand Up @@ -1650,7 +1650,7 @@ algorithm
(b,cache,compileDir,executable,_,outputFormat_str,_,simflags,resultValues,vals,dirs) = buildModel(cache,env,vals,msg);
if b then
Values.REAL(linearizeTime) = getListNthShowError(vals,"try to get stop time",0,2);
executableSuffixedExe = stringAppend(executable, Autoconf.exeExt);
executableSuffixedExe = stringAppend(executable, getSimulationExtension(Config.simCodeTarget(),Autoconf.platform));
logFile = stringAppend(executable,".log");
if System.regularFileExists(logFile) then
0 = System.removeFile(logFile);
Expand All @@ -1660,7 +1660,7 @@ algorithm
System.realtimeTick(ClockIndexes.RT_CLOCK_SIMULATE_SIMULATION);
SimulationResults.close() "Windows cannot handle reading and writing to the same file from different processes like any real OS :(";

if 0 == setPathsAndSystemCall(sim_call, dirs, logFile) then
if 0 == System.systemCall(sim_call, logFile) then
result_file = stringAppendList(List.consOnTrue(not Testsuite.isRunning(),compileDir,{executable,"_res.",outputFormat_str}));
timeSimulation = System.realtimeTock(ClockIndexes.RT_CLOCK_SIMULATE_SIMULATION);
timeTotal = System.realtimeTock(ClockIndexes.RT_CLOCK_SIMULATE_TOTAL);
Expand Down Expand Up @@ -1725,7 +1725,7 @@ algorithm
sim_call = stringAppendList({"\"",exeDir,executableSuffixedExe,"\""," ",simflags});
System.realtimeTick(ClockIndexes.RT_CLOCK_SIMULATE_SIMULATION);
SimulationResults.close() "Windows cannot handle reading and writing to the same file from different processes like any real OS :(";
resI = setPathsAndSystemCall(sim_call, dirs, logFile);
resI = System.systemCall(sim_call, logFile);
timeSimulation = System.realtimeTock(ClockIndexes.RT_CLOCK_SIMULATE_SIMULATION);
else
result_file = "";
Expand Down Expand Up @@ -3241,18 +3241,23 @@ output String outString;
algorithm
outString:=match(inString,inString2)
local
// We now use a bat script, even for the C runtime, on Windows.
case ("C","WIN64")
then ".bat";
case ("C","WIN32")
then ".bat";
case ("Cpp","WIN32")
then ".bat";
then ".bat";
case ("Cpp","WIN64")
then ".bat";
then ".bat";
case ("Cpp","Unix")
then ".sh";
then ".sh";
case ("omsicpp","WIN64")
then ".bat";
case ("omsicpp","WIN32")
then ".bat";
then ".bat";
case ("omsicpp","Unix")
then ".sh";
then ".sh";
else Autoconf.exeExt;
end match;
end getSimulationExtension;
Expand Down Expand Up @@ -8750,58 +8755,6 @@ algorithm
end if;
end findConversionPath;


protected function setPathsAndSystemCall
"Set the neccesary paths based on library dirs (e.g. those from annotations)
and then issue the system command. These dirs/locations are added to the PATH
in order to find the neccsary dlls at runtime for Windows simulation executables.
NOTE: this function expects the 'link command' as the second argument. This will
look something like
{\"-LC:/Users/username/AppData/Roaming/.openmodelica/libraries/Buildings/Resources/Library/win64\",
\"-LC:/Users/username/AppData/Roaming/.openmodelica/libraries/Buildings/Resources/Library\",
...}
The function will check for strings that start with \"-L and then trims it to get the
corrseponding directory.
If you want something more general write another function and generalize this.
"
input String systemCallStr;
input list<String> libsAndLinkDirs;
input String logFile;
output Integer returnCode;
protected
String oldPath, newPath;
list<String> linkDirs = {};
algorithm

// If not Windows we do not need to modify the PATH. rpaths are set
// when the executable is built and the libs will find their depenencies just fine.
if not Autoconf.os == "Windows_NT" then
returnCode := System.systemCall(systemCallStr, logFile);
return;
end if;

// Otherwise (on Windows) we modify the path with the given additional dirs
// (e.g., Modelica library resource directories) and issue the command.
// We will then reset the path back.
newPath := "";
for str in libsAndLinkDirs loop
if Util.stringStartsWith("\"-L", str) then
newPath := newPath + System.trim(str, "\"-L") + ";";
end if;
end for;

oldPath := System.readEnv("PATH");
newPath := System.stringReplace(newPath, "/", "\\") + oldPath;
// print("Path set: " + newPath + "\n");
System.setEnv("PATH", newPath, true);

returnCode := System.systemCall(systemCallStr, logFile);

System.setEnv("PATH", oldPath, true);

end setPathsAndSystemCall;

annotation(__OpenModelica_Interface="backend");

end CevalScriptBackend;
30 changes: 21 additions & 9 deletions OMCompiler/Compiler/SimCode/SimCodeFunctionUtil.mo
Expand Up @@ -2015,20 +2015,33 @@ protected function getLinkerLibraryPaths"Builds search paths for the linker to f
input Absyn.Path path;
input list<String> inLibs;
output list<String> libPaths;
protected
String installationDir;
algorithm
libPaths := matchcontinue(uri,path,inLibs)
installationDir := Settings.getInstallationDirectoryPath();

_ := matchcontinue(uri,path,inLibs)
local
String str, platform1, platform2;
case(_, _,{"-lWinmm"}) guard Autoconf.os=="Windows_NT"
algorithm
//Winmm has to be linked from the windows system but not from the resource directories.
//This is a fix for M_DD since otherwise the dummy pthread.dll that breaks the built will be linked
then {(Settings.getInstallationDirectoryPath() + "/lib/" + Autoconf.triple + "/omc")};
libPaths := {(installationDir + "/lib/" + Autoconf.triple + "/omc")};
then ();
case(, _,_)
equation
platform1 = uri + "/" + System.openModelicaPlatform();
platform2 = uri + "/" + System.modelicaPlatform();
then uri::platform2::platform1::(Settings.getHomeDir(false)+"/.openmodelica/binaries/"+AbsynUtil.pathFirstIdent(path))::
(Settings.getInstallationDirectoryPath() + "/lib/")::(Settings.getInstallationDirectoryPath() + "/lib/" + Autoconf.triple + "/omc")::{};
algorithm
libPaths := {uri,
uri + "/" + System.modelicaPlatform(),
uri + "/" + System.openModelicaPlatform(),
(Settings.getHomeDir(false) + "/.openmodelica/binaries/" + AbsynUtil.pathFirstIdent(path)),
(installationDir + "/lib/"),
(installationDir + "/lib/" + Autoconf.triple + "/omc")};

if Autoconf.os == "Windows_NT" then
libPaths := List.appendElt(installationDir + "/bin/", libPaths);
end if;

then ();
end matchcontinue;
end getLinkerLibraryPaths;

Expand Down Expand Up @@ -2775,6 +2788,5 @@ algorithm
end match;
end appendCurrentCrefPrefix;


annotation(__OpenModelica_Interface="backendInterface");
end SimCodeFunctionUtil;
4 changes: 4 additions & 0 deletions OMCompiler/Compiler/SimCode/SimCodeMain.mo
Expand Up @@ -616,6 +616,10 @@ algorithm
codegenFuncs := (function runToStr(func=function SerializeTaskSystemInfo.serializeParMod(code=simCode, withOperations=Flags.isSet(Flags.INFO_XML_OPERATIONS)))) :: codegenFuncs;
end if;

if Autoconf.os == "Windows_NT" then
codegenFuncs := (function runToStr(func=function SimCodeUtil.generateRunnerBatScript(code=simCode))) :: codegenFuncs;
end if;

// Test the parallel code generator in the test suite. Should give decent results given that the task is disk-intensive.
numThreads := max(1, if Testsuite.isRunning() then min(2, System.numProcessors()) else Config.noProc());
if (not Flags.isSet(Flags.PARALLEL_CODEGEN)) or numThreads==1 then
Expand Down
64 changes: 64 additions & 0 deletions OMCompiler/Compiler/SimCode/SimCodeUtil.mo
Expand Up @@ -15286,5 +15286,69 @@ algorithm
end match;
end isArrayVar;

public function generateRunnerBatScript
"Always succeeds in order to clean-up external objects.

This function will generate a .bat file that can be used to launch the simulation
executable on Windows. The bat file will set the necessary PATHs and then launches
the executable."
input SimCode.SimCode code;
output String fileName;
protected
File.File file = File.File();
algorithm
(fileName) := matchcontinue code
local
String str, locations;
case SimCode.SIMCODE()
algorithm
fileName := code.fileNamePrefix + ".bat";
File.open(file,fileName,File.Mode.Write);

locations := getDirectoriesForDLLsFromLinkLibs(code.makefileParams.libs);
str := "@echo off\n"
+ "set PATH=" + locations + ";%PATH%;\n"
+ "set ERRORLEVEL=\n"
+ "call \"%CD%/" + code.fileNamePrefix + ".exe\"\n"
+ "set RESULT=%ERRORLEVEL%\n"
+ "\n"
+ "exit /b %RESULT%\n";
File.write(file, str);
then (fileName);
else
algorithm
Error.addInternalError("SimCodeMain.generateRunnerBatScript failed", sourceInfo());
then ("");
end matchcontinue;
end generateRunnerBatScript;

protected function getDirectoriesForDLLsFromLinkLibs
" Parse the makefileParams.libs and extract the necessary directories
to be added to the PATH for finding dependenncy libraries (DLLs on Windows).
NOTE: this function expects the 'link command' as the second argument. This will
look something like
{\"-LC:/Users/username/AppData/Roaming/.openmodelica/libraries/Buildings/Resources/Library/win64\",
\"-LC:/Users/username/AppData/Roaming/.openmodelica/libraries/Buildings/Resources/Library\",
...}
The function will check for strings that start with \"-L and then trims it to get the
corrseponding directory.

If you want something more general write another function and generalize this.
We can also fix the creation of MakefileParams to separately list out these directories
(We do have MakefileParams.libDirs which seems to be empty).
"
input list<String> libsAndLinkDirs;
output String outLocations;
algorithm

outLocations := "";
for str in libsAndLinkDirs loop
if Util.stringStartsWith("\"-L", str) then
outLocations := outLocations + System.trim(str, "\"-L") + ";";
end if;
end for;

end getDirectoriesForDLLsFromLinkLibs;

annotation(__OpenModelica_Interface="backend");
end SimCodeUtil;
40 changes: 38 additions & 2 deletions OMEdit/OMEditLIB/Simulation/SimulationOutputWidget.cpp
Expand Up @@ -574,6 +574,37 @@ void SimulationOutputWidget::compileModel()
#endif
}


/*!
* \brief getPathsFromBatFile
* Parses the fileName.bat file to get the necessary paths.
* Returns "" if it fails to parse the file as expected.
*/
QString SimulationOutputWidget::getPathsFromBatFile(QString fileName) {

QFile batFile(fileName);
batFile.open(QIODevice::ReadOnly | QIODevice::Text);

QString line;
// first line is supposed to be '@echo off'
line = batFile.readLine();
// Second line is where the PATH is set. We want that.
line = batFile.readLine();

if (!line.startsWith("set PATH=")) {
QString warnMessage = "Failed to read the neccesary PATH values from '" + fileName + "'\n"
+ "If simulation fails please check that you have the bat file and it is formatted correctly\n";
writeSimulationOutput(warnMessage, StringHandler::Error, true);
line = "";
} else {
// Strip the 'set PATH='
line.remove(0, 9);
}
batFile.close();

return line;
}

/*!
* \brief SimulationOutputWidget::runSimulationExecutable
* Runs the simulation executable.
Expand Down Expand Up @@ -602,10 +633,15 @@ void SimulationOutputWidget::runSimulationExecutable()
fileName = fileName.replace("//", "/");
// run the simulation executable to create the result file
#if defined(_WIN32)
QProcessEnvironment processEnvironment = StringHandler::simulationProcessEnvironment();

QString paths = getPathsFromBatFile(fileName + ".bat");

fileName = fileName.append(".exe");
QFileInfo fileInfo(mSimulationOptions.getFileName());
QProcessEnvironment processEnvironment = StringHandler::simulationProcessEnvironment();
processEnvironment.insert("PATH", fileInfo.absoluteDir().absolutePath() + ";" + processEnvironment.value("PATH"));
paths = fileInfo.absoluteDir().absolutePath() + ";" + paths;

processEnvironment.insert("PATH", paths + ";" + processEnvironment.value("PATH"));
mpSimulationProcess->setProcessEnvironment(processEnvironment);
#endif
// make the output tab enabled and current
Expand Down
1 change: 1 addition & 0 deletions OMEdit/OMEditLIB/Simulation/SimulationOutputWidget.h
Expand Up @@ -139,6 +139,7 @@ class SimulationOutputWidget : public QWidget
void deleteIntermediateCompilationFiles();
void writeSimulationOutput(QString output, StringHandler::SimulationMessageType type, bool textFormat);
void simulationProcessFinishedHelper();
QString getPathsFromBatFile(QString fileName);
private slots:
void cancelCompilationOrSimulation();
void openTransformationalDebugger();
Expand Down

0 comments on commit 56e1f47

Please sign in to comment.