Snap for 5374457 from 729bc5978e09f803c12e26e377f18bd022166cc7 to qt-release

Change-Id: I6ca7d6797d846942022f56c59e0c439047898932
diff --git a/include/linkerconfig/configwriter.h b/include/linkerconfig/configwriter.h
new file mode 100644
index 0000000..9bc0f5f
--- /dev/null
+++ b/include/linkerconfig/configwriter.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+#pragma once
+
+#include <sstream>
+#include <string>
+
+namespace android {
+namespace linkerconfig {
+namespace modules {
+
+class ConfigWriter {
+ public:
+  void SetPrefix(const std::string& prefix);
+  void ResetPrefix();
+  void WriteLine(const std::string& line);
+  void WriteLine(const char* format, ...);
+  std::string ToString();
+
+ private:
+  std::stringstream content_;
+  std::string prefix_;
+
+  std::string ResolveVariables(const std::string& str);
+};
+
+}  // namespace modules
+}  // namespace linkerconfig
+}  // namespace android
\ No newline at end of file
diff --git a/include/linkerconfig/link.h b/include/linkerconfig/link.h
index fada5b3..ed8d8a8 100644
--- a/include/linkerconfig/link.h
+++ b/include/linkerconfig/link.h
@@ -19,7 +19,8 @@
 #include <utility>
 #include <vector>
 
-#include <android-base/logging.h>
+#include "linkerconfig/configwriter.h"
+#include "linkerconfig/log.h"
 
 #define LOG_TAG "linkerconfig"
 
@@ -36,7 +37,7 @@
   }
   template <typename T, typename... Args>
   void AddSharedLib(T&& lib_name, Args&&... lib_names);
-  std::string GenerateConfig();
+  void WriteConfig(ConfigWriter& writer);
 
  private:
   const std::string origin_namespace_;
diff --git a/include/linkerconfig/log.h b/include/linkerconfig/log.h
new file mode 100644
index 0000000..ccc8bbd
--- /dev/null
+++ b/include/linkerconfig/log.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+#pragma once
+
+#include <android-base/logging.h>
+
+#define LOG_TAG "linkerconfig"
diff --git a/include/linkerconfig/namespace.h b/include/linkerconfig/namespace.h
index 0ad4b9a..3e8c1bf 100644
--- a/include/linkerconfig/namespace.h
+++ b/include/linkerconfig/namespace.h
@@ -19,7 +19,8 @@
 #include <string>
 #include <vector>
 
-#include "link.h"
+#include "linkerconfig/configwriter.h"
+#include "linkerconfig/link.h"
 
 namespace android {
 namespace linkerconfig {
@@ -71,7 +72,7 @@
                         bool with_data_asan = true);
   std::shared_ptr<Link> CreateLink(const std::string& target_namespace,
                                    bool allow_all_shared_libs = false);
-  std::string GenerateConfig();
+  void WriteConfig(ConfigWriter& writer);
 
  private:
   const bool is_isolated_;
@@ -82,8 +83,8 @@
   std::vector<std::string> asan_search_paths_;
   std::vector<std::string> asan_permitted_paths_;
   std::map<std::string, std::shared_ptr<Link>> links_;
-  std::string GetPathString(const std::string& path_type,
-                            const std::vector<std::string>& path_list);
+  void WritePathString(ConfigWriter& writer, const std::string& path_type,
+                       const std::vector<std::string>& path_list);
 };
 }  // namespace modules
 }  // namespace linkerconfig
diff --git a/include/linkerconfig/section.h b/include/linkerconfig/section.h
index d5e37c6..b5ac994 100644
--- a/include/linkerconfig/section.h
+++ b/include/linkerconfig/section.h
@@ -21,7 +21,8 @@
 #include <utility>
 #include <vector>
 
-#include "namespace.h"
+#include "linkerconfig/configwriter.h"
+#include "linkerconfig/namespace.h"
 
 namespace android {
 namespace linkerconfig {
@@ -35,8 +36,8 @@
   std::shared_ptr<Namespace> CreateNamespace(const std::string& namespace_name,
                                              bool is_isolated = false,
                                              bool is_visible = false);
-  std::string GenerateConfig();
-  std::string GenerateBinaryPaths();
+  void WriteConfig(ConfigWriter& writer);
+  void WriteBinaryPaths(ConfigWriter& writer);
   std::string GetName();
 
  private:
diff --git a/include/linkerconfig/variables.h b/include/linkerconfig/variables.h
new file mode 100644
index 0000000..7c2c87f
--- /dev/null
+++ b/include/linkerconfig/variables.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+#pragma once
+
+#include <map>
+#include <optional>
+#include <string>
+
+namespace android {
+namespace linkerconfig {
+namespace modules {
+class Variables {
+ public:
+  static std::optional<std::string> GetValue(const std::string& key);
+  static void AddValue(const std::string& key, const std::string& value);
+
+ private:
+  static std::map<std::string, std::string> variables_;
+};
+}  // namespace modules
+}  // namespace linkerconfig
+}  // namespace android
\ No newline at end of file
diff --git a/modules/configwriter.cc b/modules/configwriter.cc
new file mode 100644
index 0000000..aed8abf
--- /dev/null
+++ b/modules/configwriter.cc
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "linkerconfig/configwriter.h"
+
+#include <cstdarg>
+#include <cstdio>
+#include <iostream>
+#include <memory>
+#include <regex>
+
+#include "linkerconfig/log.h"
+#include "linkerconfig/variables.h"
+
+constexpr const char* kVariableRegex =
+    "@\\{([^@\\{\\}:]+)(:([^@\\{\\}:]*))?\\}";
+
+namespace android {
+namespace linkerconfig {
+namespace modules {
+
+void ConfigWriter::SetPrefix(const std::string& prefix) {
+  prefix_ = prefix;
+}
+
+void ConfigWriter::ResetPrefix() {
+  prefix_ = "";
+}
+
+void ConfigWriter::WriteLine(const char* format, ...) {
+  va_list args_for_length, args;
+
+  va_start(args, format);
+  va_copy(args_for_length, args);
+
+  int length = vsnprintf(nullptr, 0, format, args_for_length);
+  va_end(args_for_length);
+
+  if (length < 0) {
+    LOG(ERROR) << "Failed to get length of the string with format " << format;
+    va_end(args);
+    return;
+  }
+
+  std::unique_ptr<char[]> formatted_string(new char[length + 1]);
+
+  int res = vsnprintf(formatted_string.get(), length + 1, format, args);
+  va_end(args);
+
+  if (res < 0) {
+    LOG(ERROR) << "Failed to write a string with format " << format;
+    return;
+  }
+
+  WriteLine(std::string(formatted_string.get()));
+}
+
+void ConfigWriter::WriteLine(const std::string& line) {
+  auto resolved_line = ResolveVariables(prefix_ + line);
+  content_ << resolved_line << std::endl;
+}
+
+std::string ConfigWriter::ToString() {
+  return content_.str();
+}
+
+std::string ConfigWriter::ResolveVariables(const std::string& str) {
+  std::string result = str;
+  std::regex variable_regex(kVariableRegex);
+  std::smatch sm;
+
+  while (std::regex_search(result, sm, variable_regex)) {
+    std::stringstream ss;
+    ss << sm.prefix();
+    auto resolved_value = Variables::GetValue(sm[1]);
+    if (resolved_value.has_value()) {
+      ss << resolved_value.value();
+    } else {
+      LOG(WARNING) << "Unable to find value for " << sm[1];
+      bool contains_default = sm[2].length() > 0;
+      if (contains_default) {
+        ss << sm[3];
+      } else {
+        LOG(FATAL) << "There is no default value defined for " << sm[1];
+      }
+    }
+    ss << ResolveVariables(sm.suffix());
+    result = ss.str();
+  }
+
+  return result;
+}
+
+}  // namespace modules
+}  // namespace linkerconfig
+}  // namespace android
\ No newline at end of file
diff --git a/modules/link.cc b/modules/link.cc
index 133cd57..64dddda 100644
--- a/modules/link.cc
+++ b/modules/link.cc
@@ -19,23 +19,21 @@
 namespace android {
 namespace linkerconfig {
 namespace modules {
-std::string Link::GenerateConfig() {
-  std::string prefix =
-      "namespace." + origin_namespace_ + ".link." + target_namespace_;
-  std::string config = "";
+void Link::WriteConfig(ConfigWriter& writer) {
+  writer.SetPrefix("namespace." + origin_namespace_ + ".link." +
+                   target_namespace_);
   if (allow_all_shared_libs_) {
-    config = prefix + ".allow_all_shared_libs = true\n";
+    writer.WriteLine(".allow_all_shared_libs = true");
   } else {
     bool is_first = true;
 
     for (auto& lib_name : shared_libs_) {
-      config += prefix + ".shared_libs " + (is_first ? "= " : "+= ") +
-                lib_name + "\n";
+      writer.WriteLine(".shared_libs %s %s",
+                       is_first ? "=" : "+=", lib_name.c_str());
       is_first = false;
     }
   }
-
-  return config;
+  writer.ResetPrefix();
 }
 }  // namespace modules
 }  // namespace linkerconfig
diff --git a/modules/namespace.cc b/modules/namespace.cc
index c2c3ea2..486691c 100644
--- a/modules/namespace.cc
+++ b/modules/namespace.cc
@@ -16,7 +16,7 @@
 
 #include "linkerconfig/namespace.h"
 
-#include <android-base/logging.h>
+#include "linkerconfig/log.h"
 
 #define LOG_TAG "linkerconfig"
 
@@ -26,17 +26,15 @@
 
 constexpr const char* kDataAsanPath = "/data/asan";
 
-std::string Namespace::GetPathString(const std::string& path_type,
-                                     const std::vector<std::string>& path_list) {
-  std::string prefix = "namespace." + name_ + "." + path_type + ".paths ";
-  std::string path_string = "";
+void Namespace::WritePathString(ConfigWriter& writer,
+                                const std::string& path_type,
+                                const std::vector<std::string>& path_list) {
+  std::string prefix = path_type + ".paths ";
   bool is_first = true;
   for (auto& path : path_list) {
-    path_string += prefix + (is_first ? "= " : "+= ") + path + "\n";
+    writer.WriteLine(prefix + (is_first ? "= " : "+= ") + path);
     is_first = false;
   }
-
-  return path_string;
 }
 
 std::shared_ptr<Link> Namespace::CreateLink(const std::string& target_namespace,
@@ -53,41 +51,40 @@
   return new_link;
 }
 
-std::string Namespace::GenerateConfig() {
-  std::string config = "";
-  std::string prefix = "namespace." + name_ + ".";
+void Namespace::WriteConfig(ConfigWriter& writer) {
+  writer.SetPrefix("namespace." + name_ + ".");
 
-  config += prefix + "isolated = " + (is_isolated_ ? "true" : "false") + "\n";
+  writer.WriteLine("isolated = %s", is_isolated_ ? "true" : "false");
 
   if (is_visible_) {
-    config += prefix + "visible = true\n";
+    writer.WriteLine("visible = true");
   }
 
-  config += GetPathString("search", search_paths_);
-  config += GetPathString("permitted", permitted_paths_);
-  config += GetPathString("asan.search", asan_search_paths_);
-  config += GetPathString("asan.permitted", asan_permitted_paths_);
+  WritePathString(writer, "search", search_paths_);
+  WritePathString(writer, "permitted", permitted_paths_);
+  WritePathString(writer, "asan.search", asan_search_paths_);
+  WritePathString(writer, "asan.permitted", asan_permitted_paths_);
 
   if (!links_.empty()) {
-    config += prefix + "links = ";
+    std::string link_list = "";
 
     bool is_first = true;
     for (auto& link : links_) {
       if (!is_first) {
-        config += ",";
+        link_list += ",";
       }
-      config += link.first;
+      link_list += link.first;
       is_first = false;
     }
 
-    config += "\n";
+    writer.WriteLine("links = " + link_list);
 
     for (auto& link : links_) {
-      config += link.second->GenerateConfig();
+      link.second->WriteConfig(writer);
     }
   }
 
-  return config;
+  writer.ResetPrefix();
 }
 
 void Namespace::AddSearchPath(const std::string& path, bool in_asan,
diff --git a/modules/section.cc b/modules/section.cc
index 5501ce3..98c3a94 100644
--- a/modules/section.cc
+++ b/modules/section.cc
@@ -16,7 +16,7 @@
 
 #include "linkerconfig/section.h"
 
-#include <android-base/logging.h>
+#include "linkerconfig/log.h"
 
 #define LOG_TAG "linkerconfig"
 
@@ -37,8 +37,8 @@
   return new_namespace;
 }
 
-std::string Section::GenerateConfig() {
-  std::string config = "[" + name_ + "]\n";
+void Section::WriteConfig(ConfigWriter& writer) {
+  writer.WriteLine("[%s]", name_.c_str());
 
   std::string additional_namespaces = "";
 
@@ -55,25 +55,22 @@
   }
 
   if (!is_first) {
-    config += "additional.namespaces = " + additional_namespaces + "\n";
+    writer.WriteLine("additional.namespaces = " + additional_namespaces);
   }
 
   for (auto& ns : namespaces_) {
-    config += ns.second->GenerateConfig();
+    ns.second->WriteConfig(writer);
   }
-
-  return config;
 }
 
-std::string Section::GenerateBinaryPaths() {
-  std::string binary_path = "";
-  std::string prefix = "dir." + name_ + " = ";
+void Section::WriteBinaryPaths(ConfigWriter& writer) {
+  writer.SetPrefix("dir." + name_ + " = ");
 
   for (auto& path : binary_paths_) {
-    binary_path += prefix + path + "\n";
+    writer.WriteLine(path);
   }
 
-  return binary_path;
+  writer.ResetPrefix();
 }
 
 std::string Section::GetName() {
diff --git a/modules/tests/configwriter_test.cc b/modules/tests/configwriter_test.cc
new file mode 100644
index 0000000..4c5c9fa
--- /dev/null
+++ b/modules/tests/configwriter_test.cc
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "linkerconfig/configwriter.h"
+#include "linkerconfig/variables.h"
+
+constexpr const char* kSampleContent = "Lorem ipsum dolor sit amet";
+
+constexpr const char* kExpectedResultWithPrefix =
+    R"(Lorem ipsum dolor sit amet
+SAMPLE.TEST.Text : Lorem ipsum dolor sit amet
+end of context
+)";
+
+constexpr const char* kExpectedResultWithVariables =
+    R"(/Value/Test
+/Invalid_Value/Path
+//Path2
+)";
+
+TEST(linkerconfig_configwriter, write_line) {
+  android::linkerconfig::modules::ConfigWriter writer;
+
+  writer.WriteLine(kSampleContent);
+
+  ASSERT_EQ(writer.ToString(), "Lorem ipsum dolor sit amet\n");
+}
+
+TEST(linkerconfig_configwriter, write_with_format) {
+  android::linkerconfig::modules::ConfigWriter writer;
+  writer.WriteLine("Sample text(%d) : %s", 10, kSampleContent);
+  ASSERT_EQ(writer.ToString(),
+            "Sample text(10) : Lorem ipsum dolor sit amet\n");
+}
+
+TEST(linkerconfig_configwriter, write_with_prefix) {
+  android::linkerconfig::modules::ConfigWriter writer;
+  writer.WriteLine(kSampleContent);
+  writer.SetPrefix("SAMPLE.TEST.");
+  writer.WriteLine("Text : %s", kSampleContent);
+  writer.ResetPrefix();
+  writer.WriteLine("end of context");
+
+  ASSERT_EQ(writer.ToString(), kExpectedResultWithPrefix);
+}
+
+TEST(linkerconfig_configwriter, replace_variable) {
+  android::linkerconfig::modules::ConfigWriter writer;
+
+  android::linkerconfig::modules::Variables::AddValue("Test_Prop_Q", "Value");
+  android::linkerconfig::modules::Variables::AddValue("VNDK_VER", "Q");
+
+  writer.WriteLine("/@{Test_Prop_@{VNDK_VER}}/Test");
+  writer.WriteLine("/@{Invalid_Key:Invalid_Value}/Path");
+  writer.WriteLine("/@{Invalid_Key:}/Path2");
+
+  ASSERT_EQ(writer.ToString(), kExpectedResultWithVariables);
+}
\ No newline at end of file
diff --git a/modules/tests/link_test.cc b/modules/tests/link_test.cc
index d84e3b4..a467ba4 100644
--- a/modules/tests/link_test.cc
+++ b/modules/tests/link_test.cc
@@ -16,6 +16,7 @@
 
 #include <gtest/gtest.h>
 
+#include "linkerconfig/configwriter.h"
 #include "linkerconfig/link.h"
 
 constexpr const char* kSharedLibsExpectedResult =
@@ -25,18 +26,27 @@
 )";
 
 TEST(linkerconfig_link, link_with_all_shared_libs) {
+  android::linkerconfig::modules::ConfigWriter writer;
+
   auto link = std::make_shared<android::linkerconfig::modules::Link>(
       "originalNamespace", "targetNamespace", true);
-  auto config_text = link->GenerateConfig();
+
+  link->WriteConfig(writer);
+  auto config_text = writer.ToString();
+
   ASSERT_EQ(config_text,
             "namespace.originalNamespace.link.targetNamespace.allow_all_shared_"
             "libs = true\n");
 }
 
 TEST(linkerconfig_link, link_with_shared_libs) {
+  android::linkerconfig::modules::ConfigWriter writer;
   auto link = std::make_shared<android::linkerconfig::modules::Link>(
       "originalNamespace", "targetNamespace");
   link->AddSharedLib("lib1.so", "lib2.so", "lib3.so");
-  auto config_text = link->GenerateConfig();
+
+  link->WriteConfig(writer);
+  auto config_text = writer.ToString();
+
   ASSERT_EQ(config_text, kSharedLibsExpectedResult);
 }
\ No newline at end of file
diff --git a/modules/tests/namespace_test.cc b/modules/tests/namespace_test.cc
index e6a70eb..777d8d7 100644
--- a/modules/tests/namespace_test.cc
+++ b/modules/tests/namespace_test.cc
@@ -16,6 +16,7 @@
 
 #include <gtest/gtest.h>
 
+#include "linkerconfig/configwriter.h"
 #include "modules_testbase.h"
 
 constexpr const char* kExpectedSimpleNamespaceConfig =
@@ -57,21 +58,27 @@
 )";
 
 TEST(linkerconfig_namespace, simple_namespace) {
+  android::linkerconfig::modules::ConfigWriter writer;
   auto ns = std::make_shared<android::linkerconfig::modules::Namespace>(
       "test_namespace");
+
   DecorateNamespaceWithPaths(ns);
-  auto config = ns->GenerateConfig();
+  ns->WriteConfig(writer);
+  auto config = writer.ToString();
 
   ASSERT_EQ(config, kExpectedSimpleNamespaceConfig);
 }
 
 TEST(linkerconfig_namespace, namespace_with_links) {
+  android::linkerconfig::modules::ConfigWriter writer;
   auto ns = std::make_shared<android::linkerconfig::modules::Namespace>(
       "test_namespace", /*is_isolated*/ true,
       /*is_visible*/ true);
+
   DecorateNamespaceWithPaths(ns);
   DecorateNamespaceWithLinks(ns, "target_namespace1", "target_namespace2");
-  auto config = ns->GenerateConfig();
+  ns->WriteConfig(writer);
+  auto config = writer.ToString();
 
   ASSERT_EQ(config, kExpectedNamespaceWithLinkConfig);
 }
\ No newline at end of file
diff --git a/modules/tests/section_test.cc b/modules/tests/section_test.cc
index 697e595..bba62a4 100644
--- a/modules/tests/section_test.cc
+++ b/modules/tests/section_test.cc
@@ -16,6 +16,7 @@
 
 #include <gtest/gtest.h>
 
+#include "linkerconfig/configwriter.h"
 #include "linkerconfig/section.h"
 #include "modules_testbase.h"
 
@@ -96,7 +97,9 @@
 dir.test_section = binary_path2
 dir.test_section = binary_path3
 )";
+
 TEST(linkerconfig_section, section_with_namespaces) {
+  android::linkerconfig::modules::ConfigWriter writer;
   android::linkerconfig::modules::Section section("test_section");
 
   auto default_namespace = section.CreateNamespace(
@@ -111,23 +114,28 @@
   auto namespace2 = section.CreateNamespace("namespace2");
   DecorateNamespaceWithPaths(namespace2);
 
-  auto config = section.GenerateConfig();
+  section.WriteConfig(writer);
+  auto config = writer.ToString();
   ASSERT_EQ(config, kSectionWithNamespacesExpectedResult);
 }
 
 TEST(linkerconfig_section, section_with_one_namespace) {
+  android::linkerconfig::modules::ConfigWriter writer;
   android::linkerconfig::modules::Section section("test_section");
   auto ns = section.CreateNamespace("default");
   DecorateNamespaceWithPaths(ns);
 
-  auto config = section.GenerateConfig();
+  section.WriteConfig(writer);
+  auto config = writer.ToString();
   ASSERT_EQ(config, kSectionWithOneNamespaceExpectedResult);
 }
 
 TEST(linkerconfig_section, binary_paths) {
+  android::linkerconfig::modules::ConfigWriter writer;
   android::linkerconfig::modules::Section section("test_section");
   section.AddBinaryPath("binary_path1", "binary_path2", "binary_path3");
 
-  auto binary_paths = section.GenerateBinaryPaths();
+  section.WriteBinaryPaths(writer);
+  auto binary_paths = writer.ToString();
   ASSERT_EQ(binary_paths, kSectionBinaryPathExpectedResult);
 }
\ No newline at end of file
diff --git a/modules/tests/variables_test.cc b/modules/tests/variables_test.cc
new file mode 100644
index 0000000..e4ada44
--- /dev/null
+++ b/modules/tests/variables_test.cc
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include <android-base/properties.h>
+#include <gtest/gtest.h>
+#include <chrono>
+
+#include "linkerconfig/variables.h"
+
+using android::linkerconfig::modules::Variables;
+
+TEST(linkerconfig_variables, load_from_map) {
+  Variables::AddValue("TEST_KEY", "TEST_VALUE");
+  auto value = Variables::GetValue("TEST_KEY");
+  ASSERT_TRUE(value.has_value());
+  ASSERT_EQ(value.value(), "TEST_VALUE");
+}
+
+TEST(linkerconfig_variables, load_from_property) {
+#if defined(__BIONIC__)
+  android::base::SetProperty("debug.linkerconfig.test_prop_key",
+                             "TEST_PROP_VALUE");
+  ASSERT_TRUE(android::base::WaitForProperty("debug.linkerconfig.test_prop_key",
+                                             "TEST_PROP_VALUE",
+                                             std::chrono::seconds(1)));
+  auto value = Variables::GetValue("debug.linkerconfig.test_prop_key");
+  ASSERT_TRUE(value.has_value());
+  ASSERT_EQ(value.value(), "TEST_PROP_VALUE");
+#endif
+}
+
+TEST(linkerconfig_variables, fallback_value) {
+  auto value = Variables::GetValue("INVALID_KEY");
+  ASSERT_FALSE(value.has_value());
+}
\ No newline at end of file
diff --git a/modules/variables.cc b/modules/variables.cc
new file mode 100644
index 0000000..fcc41ee
--- /dev/null
+++ b/modules/variables.cc
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include "linkerconfig/variables.h"
+
+#include <android-base/properties.h>
+
+#include "linkerconfig/log.h"
+
+namespace android {
+namespace linkerconfig {
+namespace modules {
+
+std::map<std::string, std::string> Variables::variables_;
+
+std::optional<std::string> Variables::GetValue(const std::string& variable) {
+  // If variable is in predefined key-value pair, use this value
+  if (variables_.find(variable) != variables_.end() &&
+      !variables_[variable].empty()) {
+    return {variables_[variable]};
+  }
+
+  // If variable is defined as property, use this value
+  std::string prop_value = android::base::GetProperty(variable, "");
+  if (!prop_value.empty()) {
+    return {prop_value};
+  }
+
+  // If cannot find variable, return default value
+  return std::nullopt;
+}
+
+void Variables::AddValue(const std::string& key, const std::string& value) {
+  variables_[key] = value;
+}
+}  // namespace modules
+}  // namespace linkerconfig
+}  // namespace android
\ No newline at end of file