IVGCVSW-3541 Get the paths where to load the dynamic backends from

 * Adds GetBackendPaths and IsPathValid to DynamicBackendUtils
 * Adds related unit tests

Change-Id: I94e377d92a88a4b5d48026f6ad5b4d5387d20c21
Signed-off-by: Jan Eilers <jan.eilers@arm.com>
Signed-off-by: Matteo Martincigh <matteo.martincigh@arm.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index eaaf2d6..08c693d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,6 +15,11 @@
     include(${cmake_file})
 endforeach()
 
+if (DYNAMIC_BACKEND_PATHS)
+    # It's expected to have the format: DYNAMIC_BACKEND_PATHS="PATH_1:PATH_2...:PATH_N"
+    add_definitions('-DDYNAMIC_BACKEND_PATHS="${DYNAMIC_BACKEND_PATHS}"')
+endif()
+
 include(GNUInstallDirs)
 
 add_subdirectory(samples)
@@ -451,7 +456,8 @@
 
 install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
 
-target_link_libraries(armnn ${Boost_LOG_LIBRARY} ${Boost_THREAD_LIBRARY} ${Boost_SYSTEM_LIBRARY})
+target_link_libraries(armnn ${Boost_LOG_LIBRARY} ${Boost_THREAD_LIBRARY}
+                            ${Boost_SYSTEM_LIBRARY} ${Boost_FILESYSTEM_LIBRARY})
 
 if(ARMCOMPUTENEON OR ARMCOMPUTECL)
     target_link_libraries(armnn ${ARMCOMPUTE_LIBRARIES})
diff --git a/cmake/GlobalConfig.cmake b/cmake/GlobalConfig.cmake
index 26b3ff4..8dad57a 100644
--- a/cmake/GlobalConfig.cmake
+++ b/cmake/GlobalConfig.cmake
@@ -19,6 +19,7 @@
 option(FLATC_DIR "Path to Flatbuffers compiler" OFF)
 option(TF_LITE_GENERATED_PATH "Tensorflow lite generated C++ schema location" OFF)
 option(FLATBUFFERS_ROOT "Location where the flatbuffers 'include' and 'lib' folders to be found" Off)
+option(DYNAMIC_BACKEND_PATHS "Colon seperated list of paths where to load the dynamic backends from" "")
 
 include(SelectLibraryConfigurations)
 
diff --git a/include/armnn/IRuntime.hpp b/include/armnn/IRuntime.hpp
index 0366663..3f3c998 100644
--- a/include/armnn/IRuntime.hpp
+++ b/include/armnn/IRuntime.hpp
@@ -31,6 +31,7 @@
         CreationOptions()
             : m_GpuAccTunedParameters(nullptr)
             , m_EnableGpuProfiling(false)
+            , m_DynamicBackendsPath("")
         {}
 
         /// If set, uses the GpuAcc tuned parameters from the given object when executing GPU workloads.
@@ -39,6 +40,10 @@
 
         // Setting this flag will allow the user to obtain GPU profiling information from the runtime.
         bool m_EnableGpuProfiling;
+
+        // Setting this value will override the paths set by the DYNAMIC_BACKEND_PATHS compiler directive
+        // Only a single path is allowed for the override
+        std::string m_DynamicBackendsPath;
     };
 
     static IRuntime* CreateRaw(const CreationOptions& options);
diff --git a/src/backends/backendsCommon/DynamicBackendUtils.cpp b/src/backends/backendsCommon/DynamicBackendUtils.cpp
index 0ab02d7..ae36a24 100644
--- a/src/backends/backendsCommon/DynamicBackendUtils.cpp
+++ b/src/backends/backendsCommon/DynamicBackendUtils.cpp
@@ -5,6 +5,10 @@
 
 #include "DynamicBackendUtils.hpp"
 
+#include <boost/filesystem/operations.hpp>
+#include <boost/algorithm/string.hpp>
+#include <boost/log/trivial.hpp>
+
 namespace armnn
 {
 
@@ -59,4 +63,92 @@
     return std::string(errorMessage);
 }
 
+std::vector<std::string> DynamicBackendUtils::GetBackendPaths(const std::string& overrideBackendPath)
+{
+    // Check if a path where to dynamically load the backends from is given
+    if (!overrideBackendPath.empty())
+    {
+        if (!IsPathValid(overrideBackendPath))
+        {
+            BOOST_LOG_TRIVIAL(warning) << "WARNING: The given override path for dynamic backends \""
+                                       << overrideBackendPath << "\" is not valid";
+
+            return {};
+        }
+
+        return std::vector<std::string>{ overrideBackendPath };
+    }
+
+    // Expects a colon-separated list: DYNAMIC_BACKEND_PATHS="PATH_1:PATH_2:...:PATH_N"
+    const std::string backendPaths = DYNAMIC_BACKEND_PATHS;
+
+    return GetBackendPathsImpl(backendPaths);
+}
+
+std::vector<std::string> DynamicBackendUtils::GetBackendPathsImpl(const std::string& backendPaths)
+{
+    std::unordered_set<std::string> uniqueBackendPaths;
+    std::vector<std::string> tempBackendPaths;
+    std::vector<std::string> validBackendPaths;
+
+    // Split the given list of paths
+    boost::split(tempBackendPaths, backendPaths, boost::is_any_of(":"));
+
+    for (const std::string& path : tempBackendPaths)
+    {
+        // Check whether the path is valid
+        if (!IsPathValid(path))
+        {
+            continue;
+        }
+
+        // Check whether the path is a duplicate
+        auto it = uniqueBackendPaths.find(path);
+        if (it != uniqueBackendPaths.end())
+        {
+            // The path is a duplicate
+            continue;
+        }
+
+        // Add the path to the set of unique paths
+        uniqueBackendPaths.insert(path);
+
+        // Add the path to the list of valid paths
+        validBackendPaths.push_back(path);
+    }
+
+    return validBackendPaths;
+}
+
+bool DynamicBackendUtils::IsPathValid(const std::string& path)
+{
+    if (path.empty())
+    {
+        BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path is empty";
+        return false;
+    }
+
+    boost::filesystem::path boostPath(path);
+
+    if (!boost::filesystem::exists(boostPath))
+    {
+        BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path \"" << path << "\" does not exist";
+        return false;
+    }
+
+    if (!boost::filesystem::is_directory(boostPath))
+    {
+        BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path \"" << path << "\" is not a directory";
+        return false;
+    }
+
+    if (!boostPath.is_absolute())
+    {
+        BOOST_LOG_TRIVIAL(warning) << "WARNING: The given backend path \"" << path << "\" is not absolute";
+        return false;
+    }
+
+    return true;
+}
+
 } // namespace armnn
diff --git a/src/backends/backendsCommon/DynamicBackendUtils.hpp b/src/backends/backendsCommon/DynamicBackendUtils.hpp
index 0a2d428..f0bfd3b 100644
--- a/src/backends/backendsCommon/DynamicBackendUtils.hpp
+++ b/src/backends/backendsCommon/DynamicBackendUtils.hpp
@@ -11,9 +11,14 @@
 
 #include <string>
 #include <dlfcn.h>
+#include <vector>
 
 #include <boost/format.hpp>
 
+#if !defined(DYNAMIC_BACKEND_PATHS)
+#define DYNAMIC_BACKEND_PATHS ""
+#endif
+
 namespace armnn
 {
 
@@ -28,9 +33,13 @@
 
     static bool IsBackendCompatible(const BackendVersion& backendVersion);
 
+    static std::vector<std::string> GetBackendPaths(const std::string& overrideBackendPath = "");
+    static bool IsPathValid(const std::string& path);
+
 protected:
-    /// Protected for testing purposes
+    /// Protected methods for testing purposes
     static bool IsBackendCompatibleImpl(const BackendVersion& backendApiVersion, const BackendVersion& backendVersion);
+    static std::vector<std::string> GetBackendPathsImpl(const std::string& backendPaths);
 
 private:
     static std::string GetDlError();
diff --git a/src/backends/backendsCommon/test/DynamicBackendTests.cpp b/src/backends/backendsCommon/test/DynamicBackendTests.cpp
index 10a650a..16d07c1 100644
--- a/src/backends/backendsCommon/test/DynamicBackendTests.cpp
+++ b/src/backends/backendsCommon/test/DynamicBackendTests.cpp
@@ -40,4 +40,7 @@
 ARMNN_SIMPLE_TEST_CASE(CreateDynamicBackendObjectInvalidInterface7,
                        CreateDynamicBackendObjectInvalidInterface7TestImpl);
 
+ARMNN_SIMPLE_TEST_CASE(GetBackendPaths, GetBackendPathsTestImpl)
+ARMNN_SIMPLE_TEST_CASE(GetBackendPathsOverride, GetBackendPathsOverrideTestImpl)
+
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/backends/backendsCommon/test/DynamicBackendTests.hpp b/src/backends/backendsCommon/test/DynamicBackendTests.hpp
index 55f90d4..3277811 100644
--- a/src/backends/backendsCommon/test/DynamicBackendTests.hpp
+++ b/src/backends/backendsCommon/test/DynamicBackendTests.hpp
@@ -34,6 +34,11 @@
 static std::string g_TestInvalidTestDynamicBackend6FileName = "libInvalidTestDynamicBackend6.so";
 static std::string g_TestInvalidTestDynamicBackend7FileName = "libInvalidTestDynamicBackend7.so";
 
+static std::string g_TestDynamicBackendsFileParsingSubDir1  = "backendsTestPath1/";
+static std::string g_TestDynamicBackendsFileParsingSubDir2  = "backendsTestPath2/";
+static std::string g_TestDynamicBackendsFileParsingSubDir3  = "backendsTestPath3/";
+static std::string g_TestDynamicBackendsFileParsingSubDir4  = "backendsTestPath4/";
+
 std::string GetTestDirectoryBasePath()
 {
     using namespace boost::filesystem;
@@ -455,3 +460,113 @@
     BOOST_CHECK_THROW(dynamicBackend.reset(new DynamicBackend(sharedObjectHandle)), RuntimeException);
     BOOST_TEST((dynamicBackend == nullptr));
 }
+
+void GetBackendPathsTestImpl()
+{
+    using namespace armnn;
+    using namespace boost::filesystem;
+
+    // The test covers four directories:
+    // <unit test path>/src/backends/backendsCommon/test/
+    //                                                ├─ backendsTestPath1/   -> existing, contains files
+    //                                                ├─ backendsTestPath2/   -> existing, contains files
+    //                                                ├─ backendsTestPath3/   -> existing, but empty
+    //                                                └─ backendsTestPath4/   -> not existing
+
+    std::string subDir1 = GetTestSubDirectory(g_TestDynamicBackendsFileParsingSubDir1);
+    std::string subDir2 = GetTestSubDirectory(g_TestDynamicBackendsFileParsingSubDir2);
+    std::string subDir3 = GetTestSubDirectory(g_TestDynamicBackendsFileParsingSubDir3);
+    std::string subDir4 = GetTestSubDirectory(g_TestDynamicBackendsFileParsingSubDir4);
+
+    BOOST_CHECK(exists(subDir1));
+    BOOST_CHECK(exists(subDir2));
+    BOOST_CHECK(exists(subDir3));
+    BOOST_CHECK(!exists(subDir4));
+
+    class TestDynamicBackendUtils : public DynamicBackendUtils
+    {
+    public:
+        static std::vector<std::string> GetBackendPathsImplTest(const std::string& path)
+        {
+            return GetBackendPathsImpl(path);
+        }
+    };
+
+    // No path
+    BOOST_TEST(TestDynamicBackendUtils::GetBackendPathsImplTest("").empty());
+
+    // Malformed path
+    std::string malformedDir(subDir1 + "/" + subDir1);
+    BOOST_TEST(TestDynamicBackendUtils::GetBackendPathsImplTest(malformedDir).size()==0);
+
+    // Single valid path
+    std::vector<std::string> DynamicBackendPaths2 = TestDynamicBackendUtils::GetBackendPathsImplTest(subDir1);
+    BOOST_TEST(DynamicBackendPaths2.size() == 1);
+    BOOST_TEST(DynamicBackendPaths2[0] == subDir1);
+
+    // Multiple equal and valid paths
+    std::string multipleEqualDirs(subDir1 + ":" + subDir1);
+    std::vector<std::string> DynamicBackendPaths3 = TestDynamicBackendUtils::GetBackendPathsImplTest(multipleEqualDirs);
+    BOOST_TEST(DynamicBackendPaths3.size() == 1);
+    BOOST_TEST(DynamicBackendPaths3[0] == subDir1);
+
+    // Multiple empty paths
+    BOOST_TEST(TestDynamicBackendUtils::GetBackendPathsImplTest(":::").empty());
+
+    // Multiple valid paths
+    std::string multipleValidPaths(subDir1 + ":" + subDir2 + ":" + subDir3);
+    std::vector<std::string> DynamicBackendPaths5 =
+        TestDynamicBackendUtils::GetBackendPathsImplTest(multipleValidPaths);
+    BOOST_TEST(DynamicBackendPaths5.size() == 3);
+    BOOST_TEST(DynamicBackendPaths5[0] == subDir1);
+    BOOST_TEST(DynamicBackendPaths5[1] == subDir2);
+    BOOST_TEST(DynamicBackendPaths5[2] == subDir3);
+
+    // Valid among empty paths
+    std::string validAmongEmptyDirs("::" + subDir1 + ":");
+    std::vector<std::string> DynamicBackendPaths6 =
+        TestDynamicBackendUtils::GetBackendPathsImplTest(validAmongEmptyDirs);
+    BOOST_TEST(DynamicBackendPaths6.size() == 1);
+    BOOST_TEST(DynamicBackendPaths6[0] == subDir1);
+
+    // Invalid among empty paths
+    std::string invalidAmongEmptyDirs(":" + subDir4 + "::");
+    BOOST_TEST(TestDynamicBackendUtils::GetBackendPathsImplTest(invalidAmongEmptyDirs).empty());
+
+    // Valid, invalid and empty paths
+    std::string validInvalidEmptyDirs(subDir1 + ":" + subDir4 + ":");
+    std::vector<std::string> DynamicBackendPaths8 =
+        TestDynamicBackendUtils::GetBackendPathsImplTest(validInvalidEmptyDirs);
+    BOOST_TEST(DynamicBackendPaths8.size() == 1);
+    BOOST_TEST(DynamicBackendPaths8[0] == subDir1);
+
+    // Mix of duplicates of valid, invalid and empty paths
+    std::string duplicateValidInvalidEmptyDirs(validInvalidEmptyDirs + ":" + validInvalidEmptyDirs + ":" +
+                                               subDir2 + ":" + subDir2);
+    std::vector<std::string> DynamicBackendPaths9 =
+        TestDynamicBackendUtils::GetBackendPathsImplTest(duplicateValidInvalidEmptyDirs);
+    BOOST_TEST(DynamicBackendPaths9.size() == 2);
+    BOOST_TEST(DynamicBackendPaths9[0] == subDir1);
+    BOOST_TEST(DynamicBackendPaths9[1] == subDir2);
+}
+
+void GetBackendPathsOverrideTestImpl()
+{
+    using namespace armnn;
+    using namespace boost::filesystem;
+
+    std::string subDir1 = GetTestSubDirectory(g_TestDynamicBackendsFileParsingSubDir1);
+    std::string subDir4 = GetTestSubDirectory(g_TestDynamicBackendsFileParsingSubDir4);
+
+    BOOST_CHECK(exists(subDir1));
+    BOOST_CHECK(!exists(subDir4));
+
+    // Override with valid path
+    std::vector<std::string> validResult = DynamicBackendUtils::GetBackendPaths(subDir1);
+    BOOST_TEST(validResult.size() == 1);
+    BOOST_TEST(validResult[0] == subDir1);
+
+    // Override with invalid path
+    std::vector<std::string> invalidResult = DynamicBackendUtils::GetBackendPaths(subDir4);
+    BOOST_TEST(invalidResult.empty());
+}