Commit 5f1dc200 authored by Evgeny Kusmenko's avatar Evgeny Kusmenko
Browse files

Merge branch 'ros2_test' into 'master'

Ros2

See merge request !11
parents 74e258ed ea83722e
Pipeline #102419 passed with stages
in 3 minutes and 33 seconds
......@@ -9,7 +9,7 @@
<groupId>de.monticore.lang.monticar</groupId>
<artifactId>embedded-montiarc-math-roscpp-generator</artifactId>
<version>0.1.2-SNAPSHOT</version>
<version>0.1.3-SNAPSHOT</version>
<!-- == PROJECT DEPENDENCIES ============================================= -->
......@@ -18,7 +18,7 @@
<!-- .. SE-Libraries .................................................. -->
<se-commons.version>1.7.7</se-commons.version>
<Embedded-MontiArc-Math.version>0.1.7-SNAPSHOT</Embedded-MontiArc-Math.version>
<Embedded-montiarc-math-rosmsg-generator.version>0.1.1-SNAPSHOT</Embedded-montiarc-math-rosmsg-generator.version>
<Embedded-montiarc-math-rosmsg-generator.version>0.1.2-SNAPSHOT</Embedded-montiarc-math-rosmsg-generator.version>
<!-- .. Libraries .................................................. -->
<guava.version>18.0</guava.version>
......
......@@ -18,6 +18,7 @@ import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
public class GeneratorRosCpp {
......@@ -101,19 +102,26 @@ public class GeneratorRosCpp {
apdapter.setFileContent(PrinterHelper.printClass(currentBluePrint.get(), ": public IAdapter_" + nameTargetLanguage));
GeneratorRosMsg generatorRosMsg = new GeneratorRosMsg();
generatorRosMsg.setRos2mode(ros2Mode);
for (Map.Entry<RosMsg, MCTypeReference<? extends MCTypeSymbol>> entry : languageUnitRosCppAdapter.getUsedRosMsgs().entrySet()) {
String packageName = Arrays.stream(entry.getKey().getName().split("/")).findFirst().get();
if (packageName.equals("struct_msgs")) {
generatorRosMsg.setTarget(generationTargetPath + "/" + packageName, packageName);
String ros2extra = isRos2Mode() ? "msg/" : "";
generatorRosMsg.setTarget(generationTargetPath + "/" + ros2extra + packageName, packageName);
List<FileContent> tmpFileContents = generatorRosMsg.generateStrings(entry.getValue());
tmpFileContents.forEach(fc -> fc.setFileName(packageName + "/" + fc.getFileName()));
tmpFileContents.forEach(fc -> fc.setFileName(packageName + "/" + ros2extra + fc.getFileName()));
if(ros2Mode){
lowercaseMsgFieldNames(tmpFileContents);
}
res.addAll(tmpFileContents);
}
}
if (generateCMake) {
LanguageUnitRosCMake languageUnitRosCMake = new LanguageUnitRosCMake();
res.addAll(languageUnitRosCMake.generate(component, languageUnitRosCppAdapter.getAdditionalPackages(),isRos2Mode()));
List<RosMsg> rosMsgs = new ArrayList<>(languageUnitRosCppAdapter.getUsedRosMsgs().keySet());
res.addAll(languageUnitRosCMake.generate(component, languageUnitRosCppAdapter.getAdditionalPackages(), rosMsgs, isRos2Mode()));
}
res.add(apdapter);
......@@ -121,4 +129,13 @@ public class GeneratorRosCpp {
return res;
}
private void lowercaseMsgFieldNames(List<FileContent> tmpFileContents) {
tmpFileContents.forEach(fc ->{
String fixedFileContent = Arrays.stream(fc.getFileContent().split("\\r?\\n"))
.map(line -> line.substring(0,line.indexOf(" ")) + line.substring(line.indexOf(" ")).toLowerCase())
.collect(Collectors.joining("\n"));
fc.setFileContent(fixedFileContent);
});
}
}
......@@ -2,28 +2,43 @@ package de.monticore.lang.monticar.generator.roscpp;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.instanceStructure.EMAComponentInstanceSymbol;
import de.monticore.lang.monticar.generator.roscpp.helper.NameHelper;
import de.monticore.lang.monticar.generator.roscpp.helper.TemplateHelper;
import de.monticore.lang.monticar.generator.rosmsg.RosField;
import de.monticore.lang.monticar.generator.rosmsg.RosMsg;
import de.monticore.lang.monticar.generator.rosmsg.util.FileContent;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
public class LanguageUnitRosCMake {
private String cmakeTemplate =
"cmake_minimum_required(VERSION 3.5)\n" +
"project (<name>)\n" +
"\n" +
"<packages>\n" +
"\n" +
"add_library(<name> <name>.cpp)\n" +
"set_target_properties(<name> PROPERTIES LINKER_LANGUAGE CXX)\n" +
"target_link_libraries(<name> <compName> IAdapter_<compName> <libraries>)\n" +
"target_include_directories(<name> PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} <include_dirs>)\n" +
"<dependency>\n" +
"\n" +
"export(TARGETS <name> FILE <name>.cmake)";
List<FileContent> generate(EMAComponentInstanceSymbol componentInstanceSymbol, List<String> additionalPackages, boolean isRos2Mode) {
private String genDepTemplate = "add_dependencies(<name> <deps>)";
//TODO: msg->h for ROS2
//.msg files must start with A-Z and must not contain _
//field names must all be lowercase
private List<RosMsg> getAllMsgs(List<RosMsg> rosMsgs){
List<RosMsg> result = new ArrayList<>();
for (RosMsg rosMsg : rosMsgs) {
result.add(rosMsg);
result.addAll(getAllMsgs(rosMsg.getFields().stream()
.map(RosField::getType)
.filter(t -> t instanceof RosMsg)
.map(t -> (RosMsg) t)
.collect(Collectors.toList())));
}
return result;
}
public List<FileContent> generate(EMAComponentInstanceSymbol componentInstanceSymbol, List<String> additionalPackages, List<RosMsg> rosMsgs, boolean isRos2Mode) {
List<RosMsg> structMsgs = getStructMsgs(rosMsgs);
FileContent fileContent = new FileContent();
fileContent.setFileName("CMakeLists.txt");
......@@ -48,20 +63,22 @@ public class LanguageUnitRosCMake {
.collect(Collectors.joining("\n"));
String libraries = distinctSortedPackages.stream()
.filter(p -> !p.equals("struct_msgs"))
.map(p -> "${" + p + "_LIBRARIES}")
.collect(Collectors.joining(" "));
String include_dirs = distinctSortedPackages.stream()
.filter(p -> !p.equals("struct_msgs"))
.map(p -> "${" + p + "_INCLUDE_DIRS}")
.collect(Collectors.joining(" "));
String dependency = "";
if (distinctSortedPackages.stream().filter(pack -> pack.startsWith("struct_msgs")).count() > 0) {
dependency = "add_dependencies(<name> struct_msgs_generate_messages)".replace("<name>", name);
}
// if (distinctSortedPackages.stream().filter(pack -> pack.startsWith("struct_msgs")).count() > 0) {
// dependency = "add_dependencies(<name> struct_msgs_generate_messages)".replace("<name>", name);
// }
String content = cmakeTemplate
String content = TemplateHelper.getCMakeListsTemplate()
.replace("<name>", name)
.replace("<compName>", compName)
.replace("<packages>", packages)
......@@ -69,8 +86,22 @@ public class LanguageUnitRosCMake {
.replace("<include_dirs>", include_dirs)
.replace("<dependency>", dependency);
fileContent.setFileContent(content);
List<FileContent> result = new ArrayList<>();
String genMsgString = null;
if(!isRos2Mode) {
genMsgString = getRosMsgGenerationString(structMsgs, name);
}else{
genMsgString = getRos2MsgGenerationString(name);
}
if(structMsgs.size() > 0){
content = content + "\n\n" + genMsgString;
if(isRos2Mode){
result.add(genRos2GenerationSettings(structMsgs));
result.add(genRos2GenerateMsgsPy());
}
}
fileContent.setFileContent(content);
result.add(fileContent);
FileContent cppFile = new FileContent();
......@@ -81,4 +112,75 @@ public class LanguageUnitRosCMake {
return result;
}
private List<RosMsg> getStructMsgs(List<RosMsg> rosMsgs) {
List<RosMsg> struct_msgs = getAllMsgs(rosMsgs).stream()
.filter(rosMsg -> rosMsg.getName().startsWith("struct_msgs/"))
.collect(Collectors.toList());
Set<String> names = new HashSet<>();
return struct_msgs.stream()
.filter(m -> !names.contains(m.getName()))
.peek(m -> names.add(m.getName()))
.collect(Collectors.toList());
}
private FileContent genRos2GenerateMsgsPy() {
FileContent res = new FileContent();
res.setFileName("generateMsgs.py");
res.setFileContent(TemplateHelper.getGenerateMsgsPyTemplate());
return res;
}
private String getRos2MsgGenerationString(String name) {
String res = "";
res += TemplateHelper.getRos2MsgGenTemplate();
res = res
.replace("<struct_replaced>", "all_structs")
.replace("<name>",name);
res += "\n\n";
res += genDepTemplate
.replace("<deps>", "gen_<name>_all_structs")
.replace("<name>",name);
return res;
}
private FileContent genRos2GenerationSettings(List<RosMsg> structMsgs){
FileContent res = new FileContent();
res.setFileName("rclcpp_msg_gen.json");
String msgFiles = structMsgs.stream().map(msg -> "'<cur_dir>/" + msg.getName() + ".msg'").collect(Collectors.joining(", "));
String content = "{\n";
content += "\t'template_dir':'<ros_base>/share/rosidl_generator_cpp/resource/',\n";
content += "\t'target_dependencies':[" + msgFiles +"],\n";
content += "\t'ros_interface_files':["+ msgFiles +"],\n";
content += "\t'package_name':'struct_msgs',\n";
content += "\t'output_dir':'<cur_dir>/struct_msgs/'\n";
content += "}";
res.setFileContent(content.replace("'","\""));
return res;
}
private String getRosMsgGenerationString(List<RosMsg> structMsgs, String name) {
String genCommands = structMsgs.stream()
.map(msg -> TemplateHelper.getMsgGenTemplate()
.replace("<struct_replaced>",msg.getName().replace("/","_"))
.replace("<struct>",msg.getName())
.replace("<name>",name))
.collect(Collectors.joining("\n\n"));
String genDeps = structMsgs.stream()
.map(msg -> "gen_" + name + "_" + msg.getName().replace("/","_"))
.collect(Collectors.joining(" "));
String genDependenies = genDepTemplate
.replace("<name>",name)
.replace("<deps>", genDeps);
return genCommands + "\n\n" + genDependenies;
}
}
......@@ -95,10 +95,8 @@ public class LanguageUnitRosCppAdapter {
.map(Optional::get)
.peek(topicType -> additionalPackages.add(NameHelper.getPackageOfMsgType(topicType)))
.map(type ->{
if(type.contains("/msg/")) {
String[] parts = type.split("/");
parts[parts.length - 1] = parts[parts.length - 1].substring(0, 1).toLowerCase() + parts[parts.length - 1].substring(1);
return String.join("/", parts);
if(ros2Mode) {
return NameHelper.msgTypeToSnakecase(NameHelper.addMsgToMsgType(type));
}else{
return type;
}
......@@ -131,10 +129,10 @@ public class LanguageUnitRosCppAdapter {
RosConnectionSymbol rcs = (RosConnectionSymbol) p.getMiddlewareSymbol().get();
Method method = topicToMethod.get(rcs.getTopicName().get());
String packageName = Arrays.stream(rcs.getTopicType().get().split("/")).findFirst().get();
RosMsg rosMsg = GeneratorRosMsg.getRosType(packageName, p.getTypeReference(), ros2Mode);
usedRosMsgs.put(rosMsg, p.getTypeReference());
if (!rcs.getMsgField().isPresent()) {
String packageName = Arrays.stream(rcs.getTopicType().get().split("/")).findFirst().get();
RosMsg rosMsg = GeneratorRosMsg.getRosType(packageName, p.getTypeReference());
usedRosMsgs.put(rosMsg, p.getTypeReference());
method.addInstruction(new SetStructMsgInstruction(p, rosMsg));
} else {
SetMsgFieldInstruction tmpInstr = new SetMsgFieldInstruction(p, getMsgConverter(rcs.getMsgField().get(), p.isIncoming()));
......@@ -263,7 +261,11 @@ public class LanguageUnitRosCppAdapter {
private String getFullRosType(RosConnectionSymbol rosConnectionSymbol) {
return rosConnectionSymbol.getTopicType().get().replace("/", "::");
String topicType = rosConnectionSymbol.getTopicType().get();
if(ros2Mode){
topicType = NameHelper.addMsgToMsgType(topicType);
}
return topicType.replace("/", "::");
}
private void generateCallbacks(List<EMAPortSymbol> rosPorts, BluePrintCPP currentBluePrint) {
......@@ -296,7 +298,7 @@ public class LanguageUnitRosCppAdapter {
if (msgField == null) {
//TODO: checks?
String packageName = Arrays.stream(rosCon.getTopicType().get().split("/")).findFirst().get();
RosMsg rosMsg = GeneratorRosMsg.getRosType(packageName, portSymbol.getTypeReference());
RosMsg rosMsg = GeneratorRosMsg.getRosType(packageName, portSymbol.getTypeReference(), ros2Mode);
usedRosMsgs.put(rosMsg, portSymbol.getTypeReference());
method.addInstruction(new SetStructPortInstruction(portSymbol, rosMsg));
} else {
......
......@@ -3,6 +3,7 @@ package de.monticore.lang.monticar.generator.roscpp.helper;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.instanceStructure.EMAComponentInstanceSymbol;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarc._symboltable.cncModel.EMAPortSymbol;
import de.monticore.lang.monticar.generator.rosmsg.RosMsg;
import de.monticore.lang.monticar.struct._symboltable.StructSymbol;
import de.se_rwth.commons.logging.Log;
import java.util.ArrayList;
......@@ -87,4 +88,37 @@ public class NameHelper {
});
return res;
}
public static List<String> getAllFieldNames(StructSymbol structSymbol){
ArrayList<String> res = new ArrayList<>();
structSymbol.getStructFieldDefinitions().forEach(f -> {
if (f.getType().getReferencedSymbol() instanceof StructSymbol) {
List<String> tmpFields = getAllFieldNames((StructSymbol) f.getType().getReferencedSymbol());
tmpFields.stream()
.map(tmpF -> f.getName() + "." + tmpF)
.forEach(res::add);
} else {
res.add(f.getName());
}
});
return res;
}
public static String addMsgToMsgType(String msgType){
String[] parts = msgType.split("/");
if (!parts[1].equals("msg")) {
parts[0] = parts[0] + "/msg";
}
return String.join("/", parts);
}
public static String msgTypeToSnakecase(String type) {
String[] parts = type.split("/");
// First letter lowercase without _
parts[parts.length - 1] = parts[parts.length - 1].substring(0, 1).toLowerCase() + parts[parts.length - 1].substring(1);
// After: replace A with _a
parts[parts.length - 1] = parts[parts.length - 1].replaceAll("([A-Z])", "_$1").toLowerCase();
return String.join("/", parts);
}
}
package de.monticore.lang.monticar.generator.roscpp.helper;
import de.se_rwth.commons.logging.Log;
import org.apache.commons.io.IOUtils;
import java.util.HashMap;
import java.util.Map;
public class TemplateHelper {
private static Map<String, String> cache = new HashMap<>();
private static String getTemplate(String fileName) {
if (cache.containsKey(fileName)) {
return cache.get(fileName);
}
String tmpStr = "";
String resourceFileName = "/de/monticore/lang/monticar/generator/roscpp/" + fileName;
try {
tmpStr = IOUtils.toString(TemplateHelper.class.getResourceAsStream(resourceFileName));
} catch (Exception e) {
//Not recoverable
Log.error("Template file not found: " + resourceFileName);
}
cache.put(fileName, tmpStr);
return tmpStr;
}
public static String getCMakeListsTemplate() {
return getTemplate("CMakeLists.template");
}
public static String getMsgGenTemplate() {
return getTemplate("msgGen.template");
}
public static String getRos2MsgGenTemplate() {
return getTemplate("ros2MsgGen.template");
}
public static String getGenerateMsgsPyTemplate() {
return getTemplate("generateMsgs.py.template");
}
}
......@@ -6,9 +6,13 @@ import de.monticore.lang.monticar.generator.roscpp.util.TargetCodeInstruction;
import de.monticore.lang.monticar.generator.roscpp.helper.IndexHelper;
import de.monticore.lang.monticar.generator.roscpp.helper.NameHelper;
import de.monticore.lang.monticar.generator.rosmsg.RosMsg;
import de.monticore.lang.monticar.struct._symboltable.StructSymbol;
import de.monticore.lang.monticar.ts.MCASTTypeSymbol;
import de.se_rwth.commons.logging.Log;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class SetStructPortInstruction extends TargetCodeInstruction {
......@@ -48,8 +52,38 @@ public class SetStructPortInstruction extends TargetCodeInstruction {
.collect(Collectors.joining("\n"));
}
} else {
this.instruction = NameHelper.getAllFieldNames(rosMsg).stream()
.map(field -> "component->" + NameHelper.getPortNameTargetLanguage(port) + "." + field + " = msg->" + field + ";")
StructSymbol structSymbol = (StructSymbol) port.getTypeReference().getReferencedSymbol();
List<String> structFieldNames = NameHelper.getAllFieldNames(structSymbol);
List<String> rosMsgFieldNames = NameHelper.getAllFieldNames(rosMsg);
if(structFieldNames.size() != rosMsgFieldNames.size()){
Log.error("Struct and RosMsg don't have the same number of fields!");
}
// Struct field names can contain uppercase letters
// RosMsgs for ros2 can only contain lowercase letters
// => match each sField to exactly one rField
Map<String, String> structToMsgField = new HashMap<>();
for (String sField : structFieldNames){
boolean found = false;
for(String rField : rosMsgFieldNames){
if(sField.equals(rField) || sField.toLowerCase().equals(rField)){
if(found){
Log.error("Found more than one RosMsg field for one Struct field!");
}else{
structToMsgField.put(sField, rField);
found = true;
}
}
}
if(!found){
Log.error("No RosMsg field found for Struct field " + sField);
}
}
this.instruction = structFieldNames.stream()
.map(field -> "component->" + NameHelper.getPortNameTargetLanguage(port) + "." + field + " = msg->" + structToMsgField.get(field) + ";")
.sorted()
.collect(Collectors.joining("\n"));
}
......
cmake_minimum_required(VERSION 3.5)
project (<name>)
set (CMAKE_CXX_STANDARD 14)
set (AMENT_CMAKE_UNINSTALL_TARGET FALSE)
<packages>
add_library(<name> <name>.cpp)
set_target_properties(<name> PROPERTIES LINKER_LANGUAGE CXX)
target_link_libraries(<name> <compName> IAdapter_<compName> <libraries>)
target_include_directories(<name> PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} <include_dirs>)
<dependency>
export(TARGETS <name> FILE <name>.cmake)
\ No newline at end of file
import rosidl_generator_cpp
import rosidl_typesupport_cpp
import os
cur_dir = os.path.dirname(os.path.abspath(__file__)).replace("\\","/")
template = None
with open(cur_dir + "/rclcpp_msg_gen.json", "r") as f:
template = f.read()
if template == None:
print("Could not load template file!")
exit(1)
ros_base = os.path.dirname(os.path.abspath(rosidl_generator_cpp.__file__))
if os.name == "posix":
ros_base = os.path.abspath(ros_base + "/../../../..")
else:
ros_base = os.path.abspath(ros_base + "/../../..")
template = template.replace("<ros_base>",ros_base.replace("\\","/"))
template = template.replace("<cur_dir>",cur_dir.replace("\\","/"))
generator_arguments_file = cur_dir + "/rclcpp_msg_gen_filled.json"
with open(generator_arguments_file,"w") as f:
f.write(template)
rosidl_generator_cpp.generate_cpp(generator_arguments_file)
# rosidl_typesupport_cpp.generate_cpp(generator_arguments_file, ["rosidl_typesupport_cpp", "rosidl_typesupport_introspection_cpp" ,"rosidl_typesupport_opensplice_cpp"])
\ No newline at end of file
# Generate .h files from .msg files
add_custom_target(gen_<name>_<struct_replaced> ALL
COMMAND python /opt/ros/kinetic/lib/gencpp/gen_cpp.py ${CMAKE_CURRENT_SOURCE_DIR}/<struct>.msg -Istruct_msgs:${CMAKE_CURRENT_SOURCE_DIR}/struct_msgs -p struct_msgs -o ${CMAKE_CURRENT_SOURCE_DIR}/struct_msgs -e /opt/ros/kinetic/share/gencpp/)
\ No newline at end of file
# Generate .h files from .msg files
add_custom_target(gen_<name>_<struct_replaced> ALL
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/generateMsgs.py)
\ No newline at end of file
......@@ -9,6 +9,7 @@ import de.monticore.ModelingLanguageFamily;
import de.monticore.io.paths.ModelPath;
import de.monticore.lang.embeddedmontiarc.LogConfig;
import de.monticore.lang.embeddedmontiarc.embeddedmontiarcmath._symboltable.EmbeddedMontiArcMathLanguage;
import de.monticore.lang.embeddedmontiarc.tagging.middleware.ros.RosToEmamTagSchema;
import de.monticore.lang.monticar.Utils;
import de.monticore.lang.monticar.enumlang._symboltable.EnumLangLanguage;
import de.monticore.lang.monticar.streamunits._symboltable.StreamUnitsLanguage;
......@@ -39,7 +40,9 @@ public class AbstractSymtabTest {
public static TaggingResolver createSymTabAndTaggingResolver(String... modelPath) {
Scope scope = createSymTab(modelPath);
return new TaggingResolver(scope, Arrays.asList(modelPath));
TaggingResolver taggingResolver = new TaggingResolver(scope, Arrays.asList(modelPath));
RosToEmamTagSchema.registerTagTypes(taggingResolver);
return taggingResolver;
}
public static Scope createSymTab(String... modelPath) {
......
......@@ -53,5 +53,54 @@ public class Ros2Test extends AbstractSymtabTest{
assertTrue(fileNames.contains("CMakeLists.txt"));
}
@Test
public void testBasicStructCompGeneration() throws IOException {
TaggingResolver taggingResolver = createSymTabAndTaggingResolver("src/test/resources/");
EMAComponentInstanceSymbol componentInstanceSymbol = taggingResolver.<EMAComponentInstanceSymbol>resolve("tests.structs.basicStructComp", EMAComponentInstanceSymbol.KIND).orElse(null);
assertNotNull(componentInstanceSymbol);
TagHelper.resolveTags(taggingResolver, componentInstanceSymbol);
GeneratorRosCpp generatorRosCpp = new GeneratorRosCpp();
generatorRosCpp.setGenerationTargetPath("./target/generated-sources-rclcpp/basicStructComp/");
generatorRosCpp.setGenerateCMake(true);
generatorRosCpp.setRos2Mode(true);
List<File> files = generatorRosCpp.generateFiles(componentInstanceSymbol, taggingResolver);
List<String> fileNames = files.stream()
.map(File::getName)
.collect(Collectors.toList());
assertTrue(fileNames.contains("CMakeLists.txt"));
assertTrue(fileNames.contains("generateMsgs.py"));
assertTrue(fileNames.contains("rclcpp_msg_gen.json"));
assertTrue(fileNames.contains("TestsStructsPosition.msg"));
}