diff --git a/.gitignore b/.gitignore
index a26b8f1..8f22ba6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,6 @@ log
build
.idea
src/ros2cs
+**/metadata*.xml
src/Ros2ForUnity/Plugins
!src/Ros2ForUnity/Plugins/.gitkeep
diff --git a/build.ps1 b/build.ps1
index c240111..269483f 100644
--- a/build.ps1
+++ b/build.ps1
@@ -34,6 +34,13 @@ if($clean_install) {
Write-Host "Cleaning install directory..." -ForegroundColor White
Remove-Item -Path "$scriptPath\install" -Force -Recurse -ErrorAction Ignore
}
+
+if($standalone) {}
+ & "python3 $SCRIPTPATH\src\scripts\metadata_generator.py" --standalone
+} else {
+ & "python3 $SCRIPTPATH\src\scripts\metadata_generator.py"
+}
+
& "$scriptPath\src\ros2cs\build.ps1" @options
if($?) {
md -Force $scriptPath\install\asset | Out-Null
@@ -42,6 +49,9 @@ if($?) {
$plugin_path=Join-Path -Path $scriptPath -ChildPath "\install\asset\Ros2ForUnity\Plugins\"
Write-Host "Deploying build to $plugin_path" -ForegroundColor Green
& "$scriptPath\deploy_unity_plugins.ps1" $plugin_path
+
+ Copy-Item -Path $scriptPath\src\Ros2ForUnity\metadata_ros2cs.xml -Destination $scriptPath\install\asset\Ros2ForUnity\Plugins\Linux\x86_64\
+ Copy-Item -Path $scriptPath\src\Ros2ForUnity\metadata_ros2cs.xml -Destination $scriptPath\install\asset\Ros2ForUnity\Plugins\
} else {
Write-Host "Ros2cs build failed!" -ForegroundColor Red
exit 1
diff --git a/build.sh b/build.sh
index 02cf16d..791fcf6 100755
--- a/build.sh
+++ b/build.sh
@@ -55,9 +55,18 @@ if [ $CLEAN_INSTALL == 1 ]; then
echo "Cleaning install directory..."
rm -rf $SCRIPTPATH/install/*
fi
+
+if [ $STANDALONE == 1 ]; then
+ python3 $SCRIPTPATH/src/scripts/metadata_generator.py --standalone
+else
+ python3 $SCRIPTPATH/src/scripts/metadata_generator.py
+fi
+
if $SCRIPTPATH/src/ros2cs/build.sh $OPTIONS; then
mkdir -p $SCRIPTPATH/install/asset && cp -R $SCRIPTPATH/src/Ros2ForUnity $SCRIPTPATH/install/asset/
$SCRIPTPATH/deploy_unity_plugins.sh $SCRIPTPATH/install/asset/Ros2ForUnity/Plugins/
+ cp $SCRIPTPATH/src/Ros2ForUnity/metadata_ros2cs.xml $SCRIPTPATH/install/asset/Ros2ForUnity/Plugins/Linux/x86_64/metadata_ros2cs.xml
+ cp $SCRIPTPATH/src/Ros2ForUnity/metadata_ros2cs.xml $SCRIPTPATH/install/asset/Ros2ForUnity/Plugins/metadata_ros2cs.xml
else
echo "Ros2cs build failed!"
exit 1
diff --git a/src/Ros2ForUnity/Scripts/PostInstall.cs b/src/Ros2ForUnity/Scripts/PostInstall.cs
new file mode 100644
index 0000000..2ed65c6
--- /dev/null
+++ b/src/Ros2ForUnity/Scripts/PostInstall.cs
@@ -0,0 +1,50 @@
+// Copyright 2019-2022 Robotec.ai.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#if UNITY_EDITOR
+using System.IO;
+using UnityEngine;
+using UnityEditor;
+using UnityEditor.Build;
+using UnityEditor.Build.Reporting;
+
+namespace ROS2
+{
+
+///
+/// An internal class responsible for installing ros2-for-unity metadata files
+///
+internal class PostInstall : IPostprocessBuildWithReport
+{
+ public int callbackOrder { get { return 0; } }
+ public void OnPostprocessBuild(BuildReport report)
+ {
+ var r2fuMetadataName = "metadata_ros2_for_unity.xml";
+ var r2csMetadataName = "metadata_ros2cs.xml";
+
+ // FileUtil.CopyFileOrDirectory: All file separators should be forward ones "/".
+ var r2fuMeta = ROS2ForUnity.GetRos2ForUnityPath() + "/" + r2fuMetadataName;
+ var r2csMeta = ROS2ForUnity.GetPluginPath() + "/" + r2csMetadataName;
+ var outputDir = Directory.GetParent(report.summary.outputPath);
+ var execFilename = Path.GetFileNameWithoutExtension(report.summary.outputPath);
+ FileUtil.CopyFileOrDirectory(
+ r2fuMeta, outputDir + "/" + execFilename + "_Data/" + r2fuMetadataName);
+ FileUtil.CopyFileOrDirectory(
+ r2csMeta, outputDir + "/" + execFilename + "_Data/Plugins/" + r2csMetadataName);
+ }
+
+}
+
+}
+#endif
\ No newline at end of file
diff --git a/src/Ros2ForUnity/Scripts/ROS2ForUnity.cs b/src/Ros2ForUnity/Scripts/ROS2ForUnity.cs
index 6258940..4c98e09 100644
--- a/src/Ros2ForUnity/Scripts/ROS2ForUnity.cs
+++ b/src/Ros2ForUnity/Scripts/ROS2ForUnity.cs
@@ -17,6 +17,7 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
+using System.Xml;
namespace ROS2
{
@@ -28,6 +29,8 @@ internal class ROS2ForUnity
{
private static bool isInitialized = false;
private static string ros2ForUnityAssetFolderName = "Ros2ForUnity";
+ private XmlDocument ros2csMetadata = new XmlDocument();
+ private XmlDocument ros2ForUnityMetadata = new XmlDocument();
enum Platform
{
@@ -35,7 +38,7 @@ enum Platform
Linux
}
- private Platform GetOS()
+ private static Platform GetOS()
{
if (Application.platform == RuntimePlatform.LinuxEditor || Application.platform == RuntimePlatform.LinuxPlayer)
{
@@ -48,11 +51,11 @@ private Platform GetOS()
throw new System.NotSupportedException("Only Linux and Windows are supported");
}
- private bool InEditor() {
+ private static bool InEditor() {
return Application.isEditor;
}
- private string GetOSName()
+ private static string GetOSName()
{
switch (GetOS())
{
@@ -80,7 +83,7 @@ private string GetEnvPathVariableValue()
return Environment.GetEnvironmentVariable(GetEnvPathVariableName());
}
- private string GetPluginPath()
+ public static string GetRos2ForUnityPath()
{
char separator = Path.DirectorySeparatorChar;
string appDataPath = Application.dataPath;
@@ -89,6 +92,14 @@ private string GetPluginPath()
if (InEditor()) {
pluginPath += separator + ros2ForUnityAssetFolderName;
}
+ return pluginPath;
+ }
+
+ public static string GetPluginPath()
+ {
+ char separator = Path.DirectorySeparatorChar;
+ string ros2ForUnityPath = GetRos2ForUnityPath();
+ string pluginPath = ros2ForUnityPath;
pluginPath += separator + "Plugins";
@@ -116,7 +127,7 @@ private string GetPluginPath()
/// Note that on Linux, LD_LIBRARY_PATH as used for dlopen() is determined on process start and this change won't
/// affect it. Ros2 looks for rmw implementation based on this variable (independently) and the change
/// is effective for this process, however rmw implementation's dependencies itself are loaded by dynamic linker
- /// anyway so setting it for Linux is pointless.
+ /// anyway so setting it for Linux is pointless.
///
private void SetEnvPathVariable()
{
@@ -132,16 +143,69 @@ private void SetEnvPathVariable()
Environment.SetEnvironmentVariable(GetEnvPathVariableName(), pluginPath + envPathSep + currentPath);
}
+ public bool IsStandalone() {
+ return Convert.ToBoolean(Convert.ToInt16(GetMetadataValue(ros2csMetadata, "/ros2cs/standalone")));
+ }
+
+ public string GetROSVersion()
+ {
+ string ros2SourcedCodename = GetROSVersionSourced();
+ string ros2FromRos4UMetadata = GetMetadataValue(ros2ForUnityMetadata, "/ros2_for_unity/ros2");
+
+ // Sourced ROS2 libs takes priority
+ if (string.IsNullOrEmpty(ros2SourcedCodename)) {
+ return ros2FromRos4UMetadata;
+ }
+
+ return ros2SourcedCodename;
+ }
+
+ ///
+ /// Checks if both ros2cs and ros2-for-unity were build for the same ros version as well as
+ /// the current sourced ros version matches ros2cs binaries.
+ ///
+ public void CheckIntegrity()
+ {
+ string ros2SourcedCodename = GetROSVersionSourced();
+ string ros2FromRos2csMetadata = GetMetadataValue(ros2csMetadata, "/ros2cs/ros2");
+ string ros2FromRos4UMetadata = GetMetadataValue(ros2ForUnityMetadata, "/ros2_for_unity/ros2");
+
+ if (ros2FromRos4UMetadata != ros2FromRos2csMetadata) {
+ Debug.LogError(
+ "ROS2 versions in 'ros2cs' and 'ros2-for-unity' metadata files are not the same. " +
+ "This is caused by mixing versions/builds. Plugin might not work correctly."
+ );
+ }
+
+ if(!IsStandalone() && ros2SourcedCodename != ros2FromRos2csMetadata) {
+ Debug.LogError(
+ "ROS2 version in 'ros2cs' metadata doesn't match currently sourced version. " +
+ "This is caused by mixing versions/builds. Plugin might not work correctly."
+ );
+ }
+
+ if (IsStandalone() && !string.IsNullOrEmpty(ros2SourcedCodename)) {
+ Debug.LogError(
+ "You should not source ROS2 in 'ros2-for-unity' standalone build. " +
+ "Plugin might not work correctly."
+ );
+ }
+ }
+
+ public string GetROSVersionSourced()
+ {
+ return Environment.GetEnvironmentVariable("ROS_DISTRO");
+ }
+
///
/// Check if the ros version is supported, only applicable to non-standalone plugin versions
/// (i. e. without ros2 libraries included in the plugin).
///
- private string CheckROSVersionSourced()
+ private void CheckROSSupport(string ros2Codename)
{
- string currentVersion = Environment.GetEnvironmentVariable("ROS_DISTRO");
List supportedVersions = new List() { "foxy", "galactic" };
var supportedVersionsString = String.Join(", ", supportedVersions);
- if (string.IsNullOrEmpty(currentVersion))
+ if (string.IsNullOrEmpty(ros2Codename))
{
string errMessage = "No ROS environment sourced. You need to source your ROS2 " + supportedVersionsString
+ " environment before launching Unity (ROS_DISTRO env variable not found)";
@@ -155,9 +219,9 @@ private string CheckROSVersionSourced()
#endif
}
- if (!supportedVersions.Contains(currentVersion))
+ if (!supportedVersions.Contains(ros2Codename))
{
- string errMessage = "Currently sourced ROS version differs from supported one. Sourced: " + currentVersion
+ string errMessage = "Currently sourced ROS version differs from supported one. Sourced: " + ros2Codename
+ ", supported: " + supportedVersionsString + ".";
Debug.LogError(errMessage);
#if UNITY_EDITOR
@@ -168,8 +232,6 @@ private string CheckROSVersionSourced()
Application.Quit(ROS_BAD_VERSION_CODE);
#endif
}
- Debug.Log("Running with a supported ROS 2 version: " + currentVersion);
- return currentVersion;
}
private void RegisterCtrlCHandler()
@@ -192,28 +254,65 @@ private void ConnectLoggers()
Ros2csLogger.LogLevel = LogLevel.WARNING;
}
+ private string GetMetadataValue(XmlDocument doc, string valuePath)
+ {
+ return doc.DocumentElement.SelectSingleNode(valuePath).InnerText;
+ }
+
+ private void LoadMetadata()
+ {
+ char separator = Path.DirectorySeparatorChar;
+ try
+ {
+ ros2csMetadata.Load(GetPluginPath() + separator + "metadata_ros2cs.xml");
+ ros2ForUnityMetadata.Load(GetRos2ForUnityPath() + separator + "metadata_ros2_for_unity.xml");
+ }
+ catch (System.IO.FileNotFoundException)
+ {
+ var errMessage = "Could not find metadata files.";
+#if UNITY_EDITOR
+ EditorApplication.isPlaying = false;
+ throw new System.IO.FileNotFoundException(errMessage);
+#else
+ const int NO_METADATA = 1;
+ Application.Quit(NO_METADATA);
+#endif
+ }
+ }
+
internal ROS2ForUnity()
{
- // TODO: Find a way to determine whether we run standalone build
+ // Load metadata
+ LoadMetadata();
+ string currentRos2Version = GetROSVersion();
+ string standalone = IsStandalone() ? "standalone" : "non-standalone";
+
+ // Self checks
+ CheckROSSupport(currentRos2Version);
+ CheckIntegrity();
+
+ // Library loading
if (GetOS() == Platform.Windows) {
// Windows version can run standalone, modifies PATH to ensure all plugins visibility
SetEnvPathVariable();
} else {
- // Linux version needs to have ros2 sourced, which is checked here. It also loads plugins by absolute path
- // since LD_LIBRARY_PATH cannot be set dynamically within the process for dlopen() which is used under the hood.
- // Since libraries are built with -rpath=".", dependencies will be correcly located within plugins directory.
- // For foxy, it is also necessary to use modified version of librcpputils to resolve custom msgs packages.
- string currentRosVersion = CheckROSVersionSourced();
+ // For foxy, it is necessary to use modified version of librcpputils to resolve custom msgs packages.
ROS2.GlobalVariables.absolutePath = GetPluginPath() + "/";
- if (currentRosVersion == "foxy") {
+ if (currentRos2Version == "foxy") {
ROS2.GlobalVariables.preloadLibrary = true;
ROS2.GlobalVariables.preloadLibraryName = "librcpputils.so";
}
}
+
+ // Initialize
ConnectLoggers();
Ros2cs.Init();
RegisterCtrlCHandler();
+ string rmwImpl = Ros2cs.GetRMWImplementation();
+
+ Debug.Log("ROS2 version: " + currentRos2Version + ". Build type: " + standalone + ". RMW: " + rmwImpl);
+
#if UNITY_EDITOR
EditorApplication.playModeStateChanged += this.EditorPlayStateChanged;
EditorApplication.quitting += this.DestroyROS2ForUnity;
diff --git a/src/scripts/metadata_generator.py b/src/scripts/metadata_generator.py
new file mode 100644
index 0000000..365185f
--- /dev/null
+++ b/src/scripts/metadata_generator.py
@@ -0,0 +1,72 @@
+# Copyright 2019-2022 Robotec.ai.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import xml.etree.ElementTree as ET
+from xml.dom import minidom
+import subprocess
+import pathlib
+
+parser = argparse.ArgumentParser(description='Generate metadata file for ros2-for-unity.')
+parser.add_argument('--standalone', action='store_true', help='is a standalone build')
+args = parser.parse_args()
+
+def get_git_commit(working_directory) -> str:
+ return subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=working_directory).decode('ascii').strip()
+
+def get_git_description(working_directory) -> str:
+ return subprocess.check_output(['git', 'describe', '--tags', '--always'], cwd=working_directory).decode('ascii').strip()
+
+def get_commit_date(working_directory) -> str:
+ return subprocess.check_output(['git', 'show', '-s', '--format=%ci'], cwd=working_directory).decode('ascii').strip()
+
+def get_git_abbrev(working_directory) -> str:
+ return subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], cwd=working_directory).decode('ascii').strip()
+
+def get_ros2_for_unity_root_path() -> pathlib.Path:
+ return pathlib.Path(__file__).parents[2]
+
+def get_ros2_for_unity_path() -> pathlib.Path:
+ return pathlib.Path(__file__).parents[1].joinpath("Ros2ForUnity")
+
+def get_ros2cs_path() -> pathlib.Path:
+ return pathlib.Path(__file__).parents[1].joinpath("ros2cs")
+
+def get_ros2_path() -> pathlib.Path:
+ return get_ros2cs_path().joinpath("src").joinpath("ros2").joinpath("rcl_interfaces")
+
+ros2_for_unity = ET.Element("ros2_for_unity")
+ET.SubElement(ros2_for_unity, "ros2").text = get_git_abbrev(get_ros2_path())
+ros2_for_unity_version = ET.SubElement(ros2_for_unity, "version")
+ET.SubElement(ros2_for_unity_version, "sha").text = get_git_commit(get_ros2_for_unity_root_path())
+ET.SubElement(ros2_for_unity_version, "desc").text = get_git_description(get_ros2_for_unity_root_path())
+ET.SubElement(ros2_for_unity_version, "date").text = get_commit_date(get_ros2_for_unity_root_path())
+
+ros2_cs = ET.Element("ros2cs")
+ET.SubElement(ros2_cs, "ros2").text = get_git_abbrev(get_ros2_path())
+ros2_cs_version = ET.SubElement(ros2_cs, "version")
+ET.SubElement(ros2_cs_version, "sha").text = get_git_commit(get_ros2cs_path())
+ET.SubElement(ros2_cs_version, "desc").text = get_git_description(get_ros2cs_path())
+ET.SubElement(ros2_cs_version, "date").text = get_commit_date(get_ros2cs_path())
+ET.SubElement(ros2_cs, "standalone").text = str(int(args.standalone))
+
+rf2u_xmlstr = minidom.parseString(ET.tostring(ros2_for_unity)).toprettyxml(indent=" ")
+metadata_rf2u_file = get_ros2_for_unity_path().joinpath("metadata_ros2_for_unity.xml")
+with open(str(metadata_rf2u_file), "w") as f:
+ f.write(rf2u_xmlstr)
+
+r2cs_xmlstr = minidom.parseString(ET.tostring(ros2_cs)).toprettyxml(indent=" ")
+metadata_r2cs_file = get_ros2_for_unity_path().joinpath("metadata_ros2cs.xml")
+with open(str(metadata_r2cs_file), "w") as f:
+ f.write(r2cs_xmlstr)