Skip to content

Commit

Permalink
Source-code-fmus on Windows using CMake (#8698)
Browse files Browse the repository at this point in the history
* Adding CMakeLists.txt.in to compile source-code FMUs
* Cross-compiling on Windows will automatically use CMake build instead of autotools build
* Added compiler flag `--fmuCMakeBuild` to force enabeling or disabeling CMake build
* Added test case
  • Loading branch information
AnHeuermann committed Mar 15, 2022
1 parent 842f9ac commit d30cc41
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 20 deletions.
212 changes: 210 additions & 2 deletions OMCompiler/Compiler/Script/CevalScriptBackend.mo
Expand Up @@ -3543,6 +3543,182 @@ algorithm
end if;
end callTranslateModel;

protected function configureFMU_cmake
"Configure and build binaries with CMake for target platform"
input String platform;
input String fmutmp;
input String fmuTargetName;
input String logfile;
input Boolean isWindows;
protected
String fmuSourceDir;
String CMAKE_GENERATOR = "", CMAKE_BUILD_TYPE;
String quote, dquote, defaultFmiIncludeDirectoy;
algorithm
fmuSourceDir := fmutmp+"/sources/";
quote := "'";
dquote := if isWindows then "\"" else "'";
defaultFmiIncludeDirectoy := dquote + Settings.getInstallationDirectoryPath() + "/include/omc/c/fmi" + dquote;

// Set build type
if Flags.getConfigEnum(Flags.FMI_FILTER) == Flags.FMI_BLACKBOX or Flags.getConfigEnum(Flags.FMI_FILTER) == Flags.FMI_PROTECTED then
CMAKE_BUILD_TYPE := "-DCMAKE_BUILD_TYPE=Release";
elseif Flags.isSet(Flags.GEN_DEBUG_SYMBOLS) then
CMAKE_BUILD_TYPE := "-DCMAKE_BUILD_TYPE=Debug";
else
CMAKE_BUILD_TYPE := "-DCMAKE_BUILD_TYPE=RelWithDebInfo";
end if;

// Remove old log file
if System.regularFileExists(logfile) then
System.removeFile(logfile);
end if;

_ := match Util.stringSplitAtChar(platform, " ")
local
String cmd;
String cmakeCall;
String crossTriple, buildDir, fmiTarget;
list<String> dockerImgArgs;
Integer uid;
String cidFile, volumeID, containerID, userID;
String dockerLogFile;
case {"dynamic"}
algorithm
if isWindows then
CMAKE_GENERATOR := "-G MSYS Makefiles ";
end if;
buildDir := "build_cmake_dynamic";
cmakeCall := "cmake " + CMAKE_GENERATOR +
"-DFMI_INTERFACE_HEADER_FILES_DIRECTORY=" + defaultFmiIncludeDirectoy + " " +
CMAKE_BUILD_TYPE +
" ..";
cmd := "cd \"" + fmuSourceDir + "\" && " +
"mkdir " + buildDir + " && cd " + buildDir + " && " +
cmakeCall + " && " +
"cmake --build . --target install && " +
"cd .. && rm -rf " + buildDir;
if 0 <> System.systemCall(cmd, outFile=logfile) then
Error.addMessage(Error.SIMULATOR_BUILD_ERROR, {System.readFile(logfile)});
System.removeFile(logfile);
fail();
end if;
then();
case crossTriple::"docker"::"run"::dockerImgArgs
algorithm
uid := System.getuid();
cidFile := fmutmp+".cidfile";

// Temp log file outside of Docker volume
dockerLogFile := crossTriple + ".tmp.log";

// Create a docker volume for the FMU since we can't forward volumes
// to the docker run command depending on where the FMU was generated (inside another volume)
cmd := "docker volume create";
runDockerCmd(cmd, dockerLogFile);
volumeID := List.last(System.strtok(System.readFile(dockerLogFile), "\n"));

if System.regularFileExists(cidFile) then
System.removeFile(cidFile);
end if;
cmd := "docker run --cidfile " + cidFile + " -v " + volumeID + ":/data busybox true";
runDockerCmd(cmd, dockerLogFile, true, volumeID, "");

containerID := System.trim(System.readFile(cidFile));
System.removeFile(cidFile);

// Copy the FMU contents to the container
cmd := "docker cp " + fmutmp + " " + containerID + ":/data";
runDockerCmd(cmd, dockerLogFile, cleanup=true, volumeID=volumeID, containerID=containerID);

// Copy the FMI headers to the container
cmd := "docker cp " + defaultFmiIncludeDirectoy + " " + containerID + ":/data/fmiInclude";
runDockerCmd(cmd, dockerLogFile, cleanup=true, volumeID=volumeID, containerID=containerID);

// Build for target host
userID := (if uid<>0 then "--user " + String(uid) else "");
buildDir := "build_cmake_" + crossTriple;
if 0 <> System.regex(crossTriple, "mingw", 1) then
fmiTarget := " -DCMAKE_SYSTEM_NAME=Windows ";
elseif 0 <> System.regex(crossTriple, "apple", 1) then
fmiTarget := " -DCMAKE_SYSTEM_NAME=Darwin ";
else
fmiTarget := "";
end if;
cmakeCall := "cmake -DFMI_INTERFACE_HEADER_FILES_DIRECTORY=/fmu/fmiInclude " +
fmiTarget +
CMAKE_BUILD_TYPE +
" ..";
cmd := "docker run " + userID + " --rm -w /fmu -v " + volumeID + ":/fmu -e CROSS_TRIPLE=" + crossTriple + " " + stringDelimitList(dockerImgArgs," ") +
" sh -c " + dquote +
"cd " + dquote + "/fmu/" + fmuSourceDir + dquote + " && " +
"mkdir " + buildDir + " && cd " + buildDir + " && " +
cmakeCall + " && " +
"cmake --build . && make install && " +
"cd .. && rm -rf " + buildDir +
dquote;
runDockerCmd(cmd, dockerLogFile, cleanup=true, volumeID=volumeID, containerID=containerID);

// Copy the files back from the volume (via the container) to the filesystem.
// Docker cp can't handle too long names on Windows.
// Workaround: Zip it in the container, copy it to host, unzip it
if isWindows then
cmd := "docker run " + userID + " --rm -w /fmu -v " + volumeID + ":/fmu " + stringDelimitList(dockerImgArgs," ") +
" tar -zcf comp-fmutmp.tar.gz " + fmutmp;
runDockerCmd(cmd, dockerLogFile, cleanup=true, volumeID=volumeID, containerID=containerID);

cmd := "docker cp " + containerID + ":/data/comp-fmutmp.tar.gz .";
runDockerCmd(cmd, dockerLogFile, cleanup=true, volumeID=volumeID, containerID=containerID);
System.systemCall("tar zxf comp-fmutmp.tar.gz && rm comp-fmutmp.tar.gz");
else
cmd := "docker cp " + containerID + ":/data/" + fmutmp + "/ .";
runDockerCmd(cmd, dockerLogFile, cleanup=false, volumeID=volumeID, containerID=containerID);
end if;

// Cleanup
System.systemCall("docker rm " + containerID);
System.systemCall("docker volume rm " + volumeID);

// Copy log file into resources directory
System.copyFile(dockerLogFile, logfile);
System.removeFile(dockerLogFile);
then();
else
algorithm
Error.addMessage(Error.SIMULATOR_BUILD_ERROR, {"Unknown/unsupported platform \"" + platform + " \" for CMake FMU build"});
then fail();
end match;
end configureFMU_cmake;

protected function runDockerCmd
"Run a docker command. Can clean up volumen and container on failure."
input String cmd;
input String logfile;
input Boolean cleanup = false;
input String volumeID = "";
input String containerID = "";
protected
Boolean verbose = false;
algorithm
System.appendFile(logfile, cmd + "\n");
if 0 <> System.systemCall(cmd, outFile=logfile) then
Error.addMessage(Error.SIMULATOR_BUILD_ERROR, {cmd + " failed:\n" + System.readFile(logfile)});

if cleanup then
if not stringEqual(containerID, "") then
System.systemCall("docker rm " + containerID);
end if;
if not stringEqual(volumeID, "") then
System.systemCall("docker volume rm " + volumeID);
end if;
end if;

fail();
elseif verbose then
print(System.readFile(logfile) +"\n");
end if;
end runDockerCmd;

protected function configureFMU
"Configures Makefile.in of FMU for traget configuration."
input String platform;
Expand Down Expand Up @@ -3808,10 +3984,10 @@ protected
SimCode.SimulationSettings simSettings;
list<String> libs;
Boolean isWindows;
Boolean useCrossCompileCmake = false;
list<String> fmiFlagsList;
Boolean needs3rdPartyLibs;
String FMUType = inFMUType;

algorithm
cache := inCache;
if not FMI.checkFMIVersion(FMUVersion) then
Expand Down Expand Up @@ -3900,10 +4076,42 @@ algorithm
needs3rdPartyLibs := false;
end if;

// Use CMake on Windows when cross-compiling with docker
_ := match (Flags.getConfigString(Flags.FMU_CMAKE_BUILD), needs3rdPartyLibs)
case ("true", _) algorithm
useCrossCompileCmake := true;
then();
case ("false", _) algorithm
useCrossCompileCmake := false;
then();
case ("default", false) algorithm
if (listLength(platforms) > 1 and isWindows) then
Error.addCompilerNotification("OS is Windows and multiple platform detected. Using CMake to build FMU.");
useCrossCompileCmake := true;
else
for platform in platforms loop
if isWindows and 1 == System.regex(platform, " docker run ", 0, true, false) then
Error.addCompilerNotification("OS is Windows and docker platform detected. Using CMake to build FMU.");
useCrossCompileCmake := true;
end if;
end for;
end if;
then();
else
algorithm
useCrossCompileCmake := false;
then();
end match;


// Configure the FMU Makefile
for platform in platforms loop
configureLogFile := System.realpath(fmutmp)+"/resources/"+System.stringReplace(listGet(Util.stringSplitAtChar(platform," "),1),"/","-")+".log";
configureFMU(platform, fmutmp, configureLogFile, isWindows, needs3rdPartyLibs);
if useCrossCompileCmake then
configureFMU_cmake(platform, fmutmp, filenameprefix, configureLogFile, isWindows);
else
configureFMU(platform, fmutmp, configureLogFile, isWindows, needs3rdPartyLibs);
end if;
if Flags.getConfigEnum(Flags.FMI_FILTER) == Flags.FMI_BLACKBOX or Flags.getConfigEnum(Flags.FMI_FILTER) == Flags.FMI_PROTECTED then
System.removeFile(configureLogFile);
end if;
Expand Down
11 changes: 10 additions & 1 deletion OMCompiler/Compiler/SimCode/SimCodeMain.mo
Expand Up @@ -742,7 +742,8 @@ algorithm
Boolean b;
Boolean needSundials = false;
String fileprefix;
String install_include_omc_dir, install_include_omc_c_dir, install_fmu_sources_dir, fmu_tmp_sources_dir;
String install_include_omc_dir, install_include_omc_c_dir, install_share_buildproject_dir, install_fmu_sources_dir, fmu_tmp_sources_dir;
String cmakelistsStr;
list<String> sourceFiles, model_desc_src_files;
list<String> dgesv_sources, cminpack_sources, simrt_c_sundials_sources, simrt_linear_solver_sources, simrt_non_linear_solver_sources;
list<String> simrt_mixed_solver_sources, fmi_export_files, model_gen_files, model_all_gen_files, shared_source_files;
Expand Down Expand Up @@ -816,6 +817,7 @@ algorithm

install_include_omc_dir := Settings.getInstallationDirectoryPath() + "/include/omc/";
install_include_omc_c_dir := install_include_omc_dir + "c/";
install_share_buildproject_dir := Settings.getInstallationDirectoryPath() + "/share/omc/runtime/c/fmi/buildproject/";
install_fmu_sources_dir := Settings.getInstallationDirectoryPath() + RuntimeSources.fmu_sources_dir;
fmu_tmp_sources_dir := fmutmp + "/sources/";

Expand Down Expand Up @@ -892,6 +894,13 @@ algorithm

Tpl.tplNoret(function CodegenFMU.translateModel(in_a_FMUVersion=FMUVersion, in_a_FMUType=FMUType, in_a_sourceFiles=model_desc_src_files), simCode);

// Copy CMakeLists.txt.in and replace @FMU_NAME_IN@ with fmu name
System.copyFile(source = install_share_buildproject_dir + "CMakeLists.txt.in",
destination = fmu_tmp_sources_dir + "CMakeLists.txt");
cmakelistsStr := System.readFile(fmu_tmp_sources_dir + "CMakeLists.txt");
cmakelistsStr := System.stringReplace(cmakelistsStr, "@FMU_NAME_IN@", simCode.fileNamePrefix);
System.writeFile(fmu_tmp_sources_dir + "CMakeLists.txt", cmakelistsStr);

Tpl.closeFile(Tpl.tplCallWithFailErrorNoArg(function CodegenFMU.fmuMakefile(a_target=Config.simulationCodeTarget(), a_simCode=simCode, a_FMUVersion=FMUVersion, a_sourceFiles=model_all_gen_files, a_runtimeObjectFiles=list(System.stringReplace(f,".c",".o") for f in shared_source_files), a_dgesvObjectFiles=list(System.stringReplace(f,".c",".o") for f in dgesv_sources), a_cminpackObjectFiles=list(System.stringReplace(f,".c",".o") for f in cminpack_sources), a_sundialsObjectFiles=list(System.stringReplace(f,".c",".o") for f in simrt_c_sundials_sources)),
txt=Tpl.redirectToFile(Tpl.emptyTxt, simCode.fileNamePrefix+".fmutmp/sources/Makefile.in")));
Tpl.closeFile(Tpl.tplCallWithFailError(CodegenFMU.settingsfile, simCode,
Expand Down
29 changes: 19 additions & 10 deletions OMCompiler/Compiler/Util/Flags.mo
Expand Up @@ -51,7 +51,7 @@ encapsulated package Flags
To add a new flag, simply add a new constant of either DebugFlag or ConfigFlag
type below, and then add it to either the allDebugFlags or allConfigFlags list
depending on which type it is.
(in FlagsUtil.mo), depending on which type it is.
"

public
Expand Down Expand Up @@ -1360,19 +1360,28 @@ constant ConfigFlag FMI_FLAGS = CONFIG_FLAG(141, "fmiFlags", NONE(), EXTERNAL(),
STRING_LIST_FLAG({}), NONE(),
Gettext.gettext("Add simulation flags to FMU. Will create <fmiPrefix>_flags.json in resources folder with given flags. Use --fmiFlags or --fmiFlags=none to disable [default]. Use --fmiFlags=default for the default simulation flags. To pass flags use e.g. --fmiFlags=s:cvode,nls:homotopy or --fmiFlags=path/to/yourFlags.json."));

constant ConfigFlag NEW_BACKEND = CONFIG_FLAG(142, "newBackend",
constant ConfigFlag FMU_CMAKE_BUILD = CONFIG_FLAG(142, "fmuCMakeBuild",
NONE(), EXTERNAL(), STRING_FLAG("default"),
SOME(STRING_DESC_OPTION({
("default", Gettext.notrans("Let omc decide if CMake should be used.")),
("true", Gettext.notrans("Use CMake to compile FMU binaries.")),
("false", Gettext.notrans("Use default GNU Autoconf toolchain to compile FMU binaries."))
})),
Gettext.gettext("Defines if FMUs will be configured and build with CMake."));

constant ConfigFlag NEW_BACKEND = CONFIG_FLAG(143, "newBackend",
NONE(), EXTERNAL(), BOOL_FLAG(false), NONE(),
Gettext.gettext("Activates experimental new backend for better array handling. This also activates the new frontend. [WIP]"));

constant ConfigFlag PARMODAUTO = CONFIG_FLAG(143, "parmodauto",
constant ConfigFlag PARMODAUTO = CONFIG_FLAG(144, "parmodauto",
NONE(), EXTERNAL(), BOOL_FLAG(false), NONE(),
Gettext.gettext("Experimental: Enable parallelization of independent systems of equations in the translated model."));

constant ConfigFlag INTERACTIVE_PORT = CONFIG_FLAG(144, "interactivePort",
constant ConfigFlag INTERACTIVE_PORT = CONFIG_FLAG(145, "interactivePort",
NONE(), EXTERNAL(), INT_FLAG(0), NONE(),
Gettext.gettext("Sets the port used by the interactive server."));

constant ConfigFlag ALLOW_NON_STANDARD_MODELICA = CONFIG_FLAG(145, "allowNonStandardModelica",
constant ConfigFlag ALLOW_NON_STANDARD_MODELICA = CONFIG_FLAG(146, "allowNonStandardModelica",
NONE(), EXTERNAL(), STRING_LIST_FLAG({
}),
SOME(STRING_DESC_OPTION({
Expand All @@ -1383,22 +1392,22 @@ constant ConfigFlag ALLOW_NON_STANDARD_MODELICA = CONFIG_FLAG(145, "allowNonStan
})),
Gettext.gettext("Flags to allow non-standard Modelica."));

constant ConfigFlag EXPORT_CLOCKS_IN_MODELDESCRIPTION = CONFIG_FLAG(146, "exportClocksInModelDescription",
constant ConfigFlag EXPORT_CLOCKS_IN_MODELDESCRIPTION = CONFIG_FLAG(147, "exportClocksInModelDescription",
NONE(), EXTERNAL(), BOOL_FLAG(false), NONE(),
Gettext.gettext("exports clocks in modeldescription.xml for fmus, The default is false."));

constant ConfigFlag LINK_TYPE = CONFIG_FLAG(147, "linkType",
constant ConfigFlag LINK_TYPE = CONFIG_FLAG(148, "linkType",
NONE(), EXTERNAL(), ENUM_FLAG(1, {("dynamic",1), ("static",2)}),
SOME(STRING_OPTION({"dynamic", "static"})),
Gettext.gettext("Sets the link type for the simulation executable.\n"+
"dynamic: libraries are dynamically linked; the executable is built very fast but is not portable because of DLL dependencies.\n"+
"static: libraries are statically linked; the executable is built more slowly but it is portable and dependency-free.\n"));

constant ConfigFlag TEARING_ALWAYS_DERIVATIVES = CONFIG_FLAG(148, "tearingAlwaysDer",
constant ConfigFlag TEARING_ALWAYS_DERIVATIVES = CONFIG_FLAG(149, "tearingAlwaysDer",
NONE(), EXTERNAL(), BOOL_FLAG(false), NONE(),
Gettext.gettext("Always choose state derivatives as iteration variables in strong components."));

constant ConfigFlag DUMP_FLAT_MODEL = CONFIG_FLAG(149, "dumpFlatModel",
constant ConfigFlag DUMP_FLAT_MODEL = CONFIG_FLAG(150, "dumpFlatModel",
NONE(), EXTERNAL(), STRING_LIST_FLAG({"all"}),
SOME(STRING_DESC_OPTION({
("flatten", Gettext.gettext("After flattening but before connection handling.")),
Expand All @@ -1409,7 +1418,7 @@ constant ConfigFlag DUMP_FLAT_MODEL = CONFIG_FLAG(149, "dumpFlatModel",
})),
Gettext.gettext("Dumps the flat model at the given stages of the frontend."));

constant ConfigFlag SIMULATION = CONFIG_FLAG(150, "simulation",
constant ConfigFlag SIMULATION = CONFIG_FLAG(151, "simulation",
SOME("u"), EXTERNAL(), BOOL_FLAG(false), NONE(),
Gettext.gettext("Simulates the last model in the given Modelica file."));

Expand Down
1 change: 1 addition & 0 deletions OMCompiler/Compiler/Util/FlagsUtil.mo
Expand Up @@ -397,6 +397,7 @@ constant list<Flags.ConfigFlag> allConfigFlags = {
Flags.FMI_FILTER,
Flags.FMI_SOURCES,
Flags.FMI_FLAGS,
Flags.FMU_CMAKE_BUILD,
Flags.NEW_BACKEND,
Flags.PARMODAUTO,
Flags.INTERACTIVE_PORT,
Expand Down
Expand Up @@ -4,6 +4,7 @@

if(WIN32)
install(FILES configure.ac
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt.in
## This should not be omc/runtime/c/fmi but rather omc/fmi. It is inconsistent
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/omc/runtime/c/fmi/buildproject
)
Expand Down Expand Up @@ -32,6 +33,7 @@ else()
${OpenModelica_SOURCE_DIR}/common/config.sub
${OpenModelica_SOURCE_DIR}/common/config.guess
${OpenModelica_SOURCE_DIR}/common/install-sh
${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt.in
## This should not be omc/runtime/c/fmi but rather omc/fmi. It is inconsistent
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/omc/runtime/c/fmi/buildproject
)
Expand Down

0 comments on commit d30cc41

Please sign in to comment.