From f44e44cb68fa01a7b090b4e15c6f0dec076fd23b Mon Sep 17 00:00:00 2001 From: Piotr Jaroszek Date: Fri, 1 Apr 2022 14:39:56 +0200 Subject: [PATCH 1/3] Standalone version fixes and improvements --- .gitignore | 1 + build.ps1 | 10 ++ build.sh | 9 ++ src/Ros2ForUnity/Scripts/ROS2ForUnity.cs | 129 ++++++++++++++++++++--- src/scripts/metadata_generator.py | 72 +++++++++++++ 5 files changed, 205 insertions(+), 16 deletions(-) create mode 100644 src/scripts/metadata_generator.py 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/ROS2ForUnity.cs b/src/Ros2ForUnity/Scripts/ROS2ForUnity.cs index 6258940..3837d17 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 { @@ -80,7 +83,7 @@ private string GetEnvPathVariableValue() return Environment.GetEnvironmentVariable(GetEnvPathVariableName()); } - private string GetPluginPath() + private string GetRos2ForUnityPath() { char separator = Path.DirectorySeparatorChar; string appDataPath = Application.dataPath; @@ -89,6 +92,14 @@ private string GetPluginPath() if (InEditor()) { pluginPath += separator + ros2ForUnityAssetFolderName; } + return pluginPath; + } + + private 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,63 @@ 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(); + Ros2csLogger.GetInstance().LogInfo("ROS2 version: " + currentRos2Version + ". Build type: " + standalone); + #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) From 375b89474694446d64e8d880eb17f9b820d011a1 Mon Sep 17 00:00:00 2001 From: Piotr Jaroszek Date: Mon, 4 Apr 2022 11:33:03 +0200 Subject: [PATCH 2/3] RMW implementation info --- src/Ros2ForUnity/Scripts/ROS2ForUnity.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Ros2ForUnity/Scripts/ROS2ForUnity.cs b/src/Ros2ForUnity/Scripts/ROS2ForUnity.cs index 3837d17..5d856cf 100644 --- a/src/Ros2ForUnity/Scripts/ROS2ForUnity.cs +++ b/src/Ros2ForUnity/Scripts/ROS2ForUnity.cs @@ -309,7 +309,9 @@ internal ROS2ForUnity() Ros2cs.Init(); RegisterCtrlCHandler(); - Ros2csLogger.GetInstance().LogInfo("ROS2 version: " + currentRos2Version + ". Build type: " + standalone); + string rmwImpl = Ros2cs.GetRMWImplementation(); + + Debug.Log("ROS2 version: " + currentRos2Version + ". Build type: " + standalone + ". RMW: " + rmwImpl); #if UNITY_EDITOR EditorApplication.playModeStateChanged += this.EditorPlayStateChanged; From bfec45f905f9fae3f5c456fe53c32fad61ce853e Mon Sep 17 00:00:00 2001 From: Piotr Jaroszek Date: Tue, 5 Apr 2022 09:55:42 +0200 Subject: [PATCH 3/3] Post install scripts for installing metadata files --- src/Ros2ForUnity/Scripts/PostInstall.cs | 50 ++++++++++++++++++++++++ src/Ros2ForUnity/Scripts/ROS2ForUnity.cs | 10 ++--- 2 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 src/Ros2ForUnity/Scripts/PostInstall.cs 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 5d856cf..4c98e09 100644 --- a/src/Ros2ForUnity/Scripts/ROS2ForUnity.cs +++ b/src/Ros2ForUnity/Scripts/ROS2ForUnity.cs @@ -38,7 +38,7 @@ enum Platform Linux } - private Platform GetOS() + private static Platform GetOS() { if (Application.platform == RuntimePlatform.LinuxEditor || Application.platform == RuntimePlatform.LinuxPlayer) { @@ -51,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()) { @@ -83,7 +83,7 @@ private string GetEnvPathVariableValue() return Environment.GetEnvironmentVariable(GetEnvPathVariableName()); } - private string GetRos2ForUnityPath() + public static string GetRos2ForUnityPath() { char separator = Path.DirectorySeparatorChar; string appDataPath = Application.dataPath; @@ -95,7 +95,7 @@ private string GetRos2ForUnityPath() return pluginPath; } - private string GetPluginPath() + public static string GetPluginPath() { char separator = Path.DirectorySeparatorChar; string ros2ForUnityPath = GetRos2ForUnityPath();