[WIP] JSON generator
diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 7031d5a..146ef36 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -181,12 +181,16 @@
   cmExportBuildAndroidMKGenerator.cxx
   cmExportBuildFileGenerator.h
   cmExportBuildFileGenerator.cxx
+  cmExportBuildJSONGenerator.h
+  cmExportBuildJSONGenerator.cxx
   cmExportFileGenerator.h
   cmExportFileGenerator.cxx
   cmExportInstallAndroidMKGenerator.h
   cmExportInstallAndroidMKGenerator.cxx
   cmExportInstallFileGenerator.h
   cmExportInstallFileGenerator.cxx
+  cmExportInstallJSONGenerator.h
+  cmExportInstallJSONGenerator.cxx
   cmExportTryCompileFileGenerator.h
   cmExportTryCompileFileGenerator.cxx
   cmExportSet.h
diff --git a/Source/cmExportBuildJSONGenerator.cxx b/Source/cmExportBuildJSONGenerator.cxx
new file mode 100644
index 0000000..4b7918e
--- /dev/null
+++ b/Source/cmExportBuildJSONGenerator.cxx
@@ -0,0 +1,298 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmExportBuildJSONGenerator.h"
+
+#include <algorithm>
+#include <memory> // IWYU pragma: keep
+#include <numeric>
+#include <sstream>
+#include <utility>
+
+#include "cmExportSet.h"
+#include "cmGeneratorExpression.h"
+#include "cmGeneratorTarget.h"
+#include "cmLinkItem.h"
+#include "cmGlobalGenerator.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmPolicies.h"
+#include "cmStateTypes.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+#include "cmake.h"
+
+cmExportBuildJSONGenerator::cmExportBuildJSONGenerator()
+{
+  this->LG = nullptr;
+  this->ExportSet = nullptr;
+}
+
+void cmExportBuildJSONGenerator::Compute(cmLocalGenerator* lg)
+{
+  this->LG = lg;
+  if (this->ExportSet) {
+    this->ExportSet->Compute(lg);
+  }
+}
+
+bool cmExportBuildJSONGenerator::GenerateMainFile(std::ostream& os)
+{
+  {
+    std::string expectedTargets;
+    std::string sep;
+    std::vector<std::string> targets;
+    this->GetTargets(targets);
+    for (std::string const& tei : targets) {
+      cmGeneratorTarget* te = this->LG->FindGeneratorTargetToUse(tei);
+      expectedTargets += sep + this->Namespace + te->GetExportName();
+      sep = " ";
+      if (this->ExportedTargets.insert(te).second) {
+        this->Exports.push_back(te);
+      } else {
+        std::ostringstream e;
+        e << "given target \"" << te->GetName() << "\" more than once.";
+        this->LG->GetGlobalGenerator()->GetCMakeInstance()->IssueMessage(
+          cmake::FATAL_ERROR, e.str(),
+          this->LG->GetMakefile()->GetBacktrace());
+        return false;
+      }
+    }
+  }
+
+  std::vector<std::string> missingTargets;
+
+  os << "{\n"
+     << "  \"targets\": {\n";
+
+  // Create all the imported targets.
+  for (auto i = this->Exports.begin(); i != this->Exports.end(); ++i) {
+    cmGeneratorTarget* gte = *i;
+
+    this->GenerateImportTargetCode(os, gte);
+
+    gte->Target->AppendBuildInterfaceIncludes();
+
+    ImportPropertyMap properties;
+
+    this->PopulateInterfaceProperty("INTERFACE_INCLUDE_DIRECTORIES", gte,
+                                    cmGeneratorExpression::BuildInterface,
+                                    properties, missingTargets);
+    this->PopulateInterfaceProperty("INTERFACE_SOURCES", gte,
+                                    cmGeneratorExpression::BuildInterface,
+                                    properties, missingTargets);
+    this->PopulateInterfaceProperty("INTERFACE_COMPILE_DEFINITIONS", gte,
+                                    cmGeneratorExpression::BuildInterface,
+                                    properties, missingTargets);
+    this->PopulateInterfaceProperty("INTERFACE_COMPILE_OPTIONS", gte,
+                                    cmGeneratorExpression::BuildInterface,
+                                    properties, missingTargets);
+    this->PopulateInterfaceProperty("INTERFACE_AUTOUIC_OPTIONS", gte,
+                                    cmGeneratorExpression::BuildInterface,
+                                    properties, missingTargets);
+    this->PopulateInterfaceProperty("INTERFACE_COMPILE_FEATURES", gte,
+                                    cmGeneratorExpression::BuildInterface,
+                                    properties, missingTargets);
+    this->PopulateInterfaceProperty("INTERFACE_POSITION_INDEPENDENT_CODE", gte,
+                                    properties);
+    const bool newCMP0022Behavior =
+      gte->GetPolicyStatusCMP0022() != cmPolicies::WARN &&
+      gte->GetPolicyStatusCMP0022() != cmPolicies::OLD;
+    if (newCMP0022Behavior) {
+      this->PopulateInterfaceLinkLibrariesProperty(
+        gte, cmGeneratorExpression::BuildInterface, properties,
+        missingTargets);
+    }
+    this->PopulateCompatibleInterfaceProperties(gte, properties);
+
+    this->GenerateInterfaceProperties(gte, os, properties);
+
+    os << "    }";
+    if (i != this->Exports.end() - 1)
+      os << ",";
+    os << "\n";
+  }
+  os << "  }\n";
+
+  // Generate import file content for each configuration.
+  for (std::string const& c : this->Configurations) {
+    this->GenerateImportConfig(os, c, missingTargets);
+  }
+
+  this->GenerateMissingTargetsCheckCode(os, missingTargets);
+
+  os << "}\n";
+
+  return true;
+}
+
+void cmExportBuildJSONGenerator::GenerateImportTargetCode(
+  std::ostream& os, const cmGeneratorTarget* target)
+{
+  std::string targetName = this->Namespace;
+  targetName += target->GetExportName();
+  os << "    \"" << targetName << "\": {\n";
+  std::string path =
+    cmSystemTools::ConvertToOutputPath(target->GetFullPath().c_str());
+  os << "      \"output\": \"" << path << "\",\n";
+}
+
+void cmExportBuildJSONGenerator::GenerateImportPropertyCode(
+  std::ostream& os, const std::string&, cmGeneratorTarget const*,
+  ImportPropertyMap const& properties)
+{
+  for (auto const& property : properties) {
+    if (property.first == "IMPORTED_SONAME") {
+    } else if (property.first == "IMPORTED_LINK_INTERFACE_LANGUAGES") {
+    } else if (property.first == "IMPORTED_LOCATION") {
+    }
+    os << "  " << property.first << " " << (property.second) << "\n";
+  }
+}
+
+void cmExportBuildJSONGenerator::GenerateMissingTargetsCheckCode(
+  std::ostream&, const std::vector<std::string>&)
+{
+}
+
+void cmExportBuildJSONGenerator::GenerateInterfaceProperties(
+  const cmGeneratorTarget* target, std::ostream& os,
+  const ImportPropertyMap& properties)
+{
+  std::string config;
+  if (!this->Configurations.empty()) {
+    config = this->Configurations[0];
+  }
+  cmExportBuildJSONGenerator::GenerateInterfaceProperties(
+    target, os, properties, cmExportBuildJSONGenerator::BUILD, config);
+}
+
+void cmExportBuildJSONGenerator::GenerateInterfaceProperties(
+  const cmGeneratorTarget* target, std::ostream& os,
+  const ImportPropertyMap& properties, GenerateType type,
+  std::string const& config)
+{
+  const bool newCMP0022Behavior =
+    target->GetPolicyStatusCMP0022() != cmPolicies::WARN &&
+    target->GetPolicyStatusCMP0022() != cmPolicies::OLD;
+  if (!newCMP0022Behavior) {
+    std::ostringstream w;
+    if (type == cmExportBuildJSONGenerator::BUILD) {
+      w << "export(TARGETS ... JSON) called with policy CMP0022";
+    } else {
+      w << "install(JSON ...) called with policy CMP0022";
+    }
+    w << " set to OLD for target " << target->Target->GetName() << ". "
+      << "The export will only work with CMP0022 set to NEW.";
+    target->Makefile->IssueMessage(cmake::AUTHOR_WARNING, w.str());
+  }
+  if (!properties.empty()) {
+    for (auto const& property : properties) {
+      if (property.first == "INTERFACE_COMPILE_OPTIONS") {
+        os << "LOCAL_CPP_FEATURES += ";
+        os << (property.second) << "\n";
+      } else if (property.first == "INTERFACE_LINK_LIBRARIES") {
+        // need to look at list in pi->second and see if static or shared
+        // FindTargetToLink
+        // target->GetLocalGenerator()->FindGeneratorTargetToUse()
+        // then add to LOCAL_CPPFLAGS
+        std::vector<std::string> libraries;
+        cmSystemTools::ExpandListArgument(property.second, libraries);
+        std::vector<std::string> deps;
+        std::vector<std::string> staticLibs;
+        std::vector<std::string> sharedLibs;
+        std::vector<std::string> ldlibs;
+        for (std::string const& lib : libraries) {
+          cmGeneratorTarget* gt =
+            target->GetLocalGenerator()->FindGeneratorTargetToUse(lib);
+          if (gt) {
+            deps.push_back(lib);
+            if (gt->GetType() == cmStateEnums::SHARED_LIBRARY ||
+                gt->GetType() == cmStateEnums::MODULE_LIBRARY) {
+              sharedLibs.push_back(lib);
+            } else {
+              staticLibs.push_back(lib);
+            }
+          } else {
+            // evaluate any generator expressions with the current
+            // build type of the makefile
+            cmGeneratorExpression ge;
+            std::unique_ptr<cmCompiledGeneratorExpression> cge = ge.Parse(lib);
+            std::string evaluated =
+              cge->Evaluate(target->GetLocalGenerator(), config);
+            bool relpath = false;
+            if (type == cmExportBuildJSONGenerator::INSTALL) {
+              relpath = lib.substr(0, 3) == "../";
+            }
+            // check for full path or if it already has a -l, or
+            // in the case of an install check for relative paths
+            // if it is full or a link library then use string directly
+            if (cmSystemTools::FileIsFullPath(evaluated) || evaluated.substr(0, 2) == "-l" || relpath) {
+              ldlibs.push_back(evaluated);
+              // if it is not a path and does not have a -l then add -l
+            } else if (!evaluated.empty()) {
+              ldlibs.push_back("-l" + evaluated);
+            }
+          }
+        }
+        auto join = [](std::string &ss, std::string &s) {
+          return ss.empty() ? s : ss + "\", \"" + s;
+        };
+        if (!deps.empty()) {
+          std::string str = std::accumulate(std::begin(deps), std::end(deps), std::string(), join);
+          os << "      \"deps\": [\"" << str << "\"],\n";
+        }
+        //if (!sharedLibs.empty()) {
+        //  std::string str = std::accumulate(std::begin(sharedLibs), std::end(sharedLibs), std::string(), join);
+        //  os << "      \"shared_libraries\": [\"" << str << "\"],\n";
+        //}
+        //if (!staticLibs.empty()) {
+        //  std::string str = std::accumulate(std::begin(staticLibs), std::end(staticLibs), std::string(), join);
+        //  os << "      \"static_libraries\": [\"" << str << "\"],\n";
+        //}
+        if (!ldlibs.empty()) {
+          std::string str = std::accumulate(std::begin(ldlibs), std::end(ldlibs), std::string(), join);
+          os << "      \"libs\": [\"" << str << "\"],\n";
+        }
+      } else if (property.first == "INTERFACE_INCLUDE_DIRECTORIES") {
+        std::string includes = property.second;
+        std::vector<std::string> includeList;
+        cmSystemTools::ExpandListArgument(includes, includeList);
+        auto join = [](std::string &ss, std::string &s) {
+          return ss.empty() ? s : ss + "\", \"" + s;
+        };
+        std::string str = std::accumulate(std::begin(includeList), std::end(includeList), std::string(), join);
+        os << "        \"include_dirs\": [\"" << str << "\"],\n";
+      } else {
+        os << "# " << property.first << " " << (property.second) << "\n";
+      }
+    }
+  }
+
+  if (target->GetType() == cmStateEnums::STATIC_LIBRARY) {
+    cmLinkImplementation const* li = target->GetLinkImplementation(config);
+    if (std::find(li->Languages.begin(), li->Languages.end(), "CXX") !=
+        li->Languages.end()) {
+    }
+  }
+
+  switch (target->GetType()) {
+    case cmStateEnums::SHARED_LIBRARY:
+      os << "      \"target_type\": \"shared_library\"\n";
+      break;
+    case cmStateEnums::MODULE_LIBRARY:
+      os << "      \"target_type\": \"module_library\"\n";
+      break;
+    case cmStateEnums::STATIC_LIBRARY:
+      os << "      \"target_type\": \"static_library\"\n";
+      break;
+    case cmStateEnums::EXECUTABLE:
+      os << "      \"target_type\": \"executable\"\n";
+      break;
+    case cmStateEnums::UTILITY:
+    case cmStateEnums::OBJECT_LIBRARY:
+    case cmStateEnums::GLOBAL_TARGET:
+    case cmStateEnums::INTERFACE_LIBRARY:
+    case cmStateEnums::UNKNOWN_LIBRARY:
+      break;
+  }
+}
diff --git a/Source/cmExportBuildJSONGenerator.h b/Source/cmExportBuildJSONGenerator.h
new file mode 100644
index 0000000..e957a7c
--- /dev/null
+++ b/Source/cmExportBuildJSONGenerator.h
@@ -0,0 +1,75 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmExportBuildJSONGenerator_h
+#define cmExportBuildJSONGenerator_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <iosfwd>
+#include <string>
+#include <vector>
+
+#include "cmExportBuildFileGenerator.h"
+#include "cmExportFileGenerator.h"
+
+class cmGeneratorTarget;
+
+/** \class cmExportBuildJSONGenerator
+ * \brief Generate a file exporting targets from a build tree.
+ *
+ * cmExportBuildJSONGenerator generates a file exporting targets from
+ * a build tree. This exports the targets to the JSON format that is
+ * intended to be consumed by other tools.
+ *
+ * This is used to implement the EXPORT() command.
+ */
+class cmExportBuildJSONGenerator : public cmExportBuildFileGenerator
+{
+public:
+  cmExportBuildJSONGenerator();
+  // this is so cmExportInstallJSONGenerator can share this
+  // function as they are almost the same
+  enum GenerateType
+  {
+    BUILD,
+    INSTALL
+  };
+  static void GenerateInterfaceProperties(cmGeneratorTarget const* target,
+                                          std::ostream& os,
+                                          const ImportPropertyMap& properties,
+                                          GenerateType type,
+                                          std::string const& config);
+
+  void Compute(cmLocalGenerator* lg);
+
+protected:
+  // Implement virtual methods from the superclass.
+  bool GenerateMainFile(std::ostream& os) override;
+  void GeneratePolicyHeaderCode(std::ostream&) override {}
+  void GeneratePolicyFooterCode(std::ostream&) override {}
+  void GenerateImportHeaderCode(std::ostream&, const std::string&) override {};
+  void GenerateImportFooterCode(std::ostream&) override {};
+  void GenerateImportTargetCode(std::ostream& os,
+                                const cmGeneratorTarget* target) override;
+  void GenerateImportPropertyCode(
+    std::ostream& os, const std::string& config,
+    cmGeneratorTarget const* target,
+    ImportPropertyMap const& properties) override;
+  void GenerateImportedFileChecksCode(
+    std::ostream&, cmGeneratorTarget*,
+    ImportPropertyMap const&, const std::set<std::string>&) override {};
+  void GenerateImportedFileCheckLoop(std::ostream&) override {};
+  void GenerateMissingTargetsCheckCode(
+    std::ostream& os, const std::vector<std::string>& missingTargets) override;
+  void GenerateExpectedTargetsCode(
+    std::ostream&, const std::string&) override {};
+
+  void GenerateImportTargetsConfig(
+    std::ostream&, const std::string&, std::string const&,
+    std::vector<std::string>&) override {};
+  void GenerateInterfaceProperties(
+    cmGeneratorTarget const* target, std::ostream& os,
+    const ImportPropertyMap& properties) override;
+};
+
+#endif
diff --git a/Source/cmExportCommand.cxx b/Source/cmExportCommand.cxx
index 9913129..e92cfcb 100644
--- a/Source/cmExportCommand.cxx
+++ b/Source/cmExportCommand.cxx
@@ -8,6 +8,7 @@
 
 #include "cmExportBuildAndroidMKGenerator.h"
 #include "cmExportBuildFileGenerator.h"
+#include "cmExportBuildJSONGenerator.h"
 #include "cmExportSetMap.h"
 #include "cmGeneratedFileStream.h"
 #include "cmGlobalGenerator.h"
@@ -34,6 +35,7 @@
   , Filename(&Helper, "FILE", &ArgumentGroup)
   , ExportOld(&Helper, "EXPORT_LINK_INTERFACE_LIBRARIES", &ArgumentGroup)
   , AndroidMKFile(&Helper, "ANDROID_MK")
+  , JSONFile(&Helper, "JSON")
 {
   this->ExportSet = nullptr;
 }
@@ -72,6 +74,11 @@
     fname = this->AndroidMKFile.GetString();
     android = true;
   }
+  bool json = false;
+  if (this->JSONFile.WasFound()) {
+    fname = this->JSONFile.GetString();
+    json = true;
+  }
   if (!this->Filename.WasFound() && fname.empty()) {
     if (args[0] != "EXPORT") {
       this->SetError("FILE <filename> option missing.");
@@ -187,6 +194,8 @@
   cmExportBuildFileGenerator* ebfg = nullptr;
   if (android) {
     ebfg = new cmExportBuildAndroidMKGenerator;
+  } else if (json) {
+    ebfg = new cmExportBuildJSONGenerator;
   } else {
     ebfg = new cmExportBuildFileGenerator;
   }
diff --git a/Source/cmExportCommand.h b/Source/cmExportCommand.h
index a5c6751..4c7d1d7 100644
--- a/Source/cmExportCommand.h
+++ b/Source/cmExportCommand.h
@@ -46,6 +46,7 @@
   cmCAString Filename;
   cmCAEnabler ExportOld;
   cmCAString AndroidMKFile;
+  cmCAString JSONFile;
 
   cmExportSet* ExportSet;
 
diff --git a/Source/cmExportInstallJSONGenerator.cxx b/Source/cmExportInstallJSONGenerator.cxx
new file mode 100644
index 0000000..8ad4f89
--- /dev/null
+++ b/Source/cmExportInstallJSONGenerator.cxx
@@ -0,0 +1,232 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmExportInstallJSONGenerator.h"
+
+#include <ostream>
+#include <stddef.h>
+
+#include "cmExportBuildJSONGenerator.h"
+#include "cmExportSet.h"
+#include "cmGeneratorTarget.h"
+#include "cmInstallExportGenerator.h"
+#include "cmInstallTargetGenerator.h"
+#include "cmStateTypes.h"
+#include "cmSystemTools.h"
+#include "cmTarget.h"
+#include "cmTargetExport.h"
+
+cmExportInstallJSONGenerator::cmExportInstallJSONGenerator(
+  cmInstallExportGenerator* iegen)
+  : cmExportInstallFileGenerator(iegen)
+{
+}
+
+bool cmExportInstallJSONGenerator::GenerateMainFile(std::ostream& os)
+{
+  std::vector<cmTargetExport*> allTargets;
+  {
+    std::string expectedTargets;
+    std::string sep;
+    for (cmTargetExport* te :
+         *this->IEGen->GetExportSet()->GetTargetExports()) {
+      expectedTargets += sep + this->Namespace + te->Target->GetExportName();
+      sep = " ";
+      if (this->ExportedTargets.insert(te->Target).second) {
+        allTargets.push_back(te);
+      } else {
+        std::ostringstream e;
+        e << "install(EXPORT \"" << this->IEGen->GetExportSet()->GetName()
+          << "\" ...) "
+          << "includes target \"" << te->Target->GetName()
+          << "\" more than once in the export set.";
+        cmSystemTools::Error(e.str().c_str());
+        return false;
+      }
+    }
+  }
+
+  std::vector<std::string> missingTargets;
+
+  os << "{\n"
+     << "  \"targets\": {\n";
+
+  bool requiresConfigFiles = false;
+  // Create all the imported targets.
+  for (auto i = allTargets.begin(); i != allTargets.end(); ++i) {
+    cmTargetExport* te = *i;
+    cmGeneratorTarget* gt = te->Target;
+
+    requiresConfigFiles =
+      requiresConfigFiles || gt->GetType() != cmStateEnums::INTERFACE_LIBRARY;
+
+    this->GenerateImportTargetCode(os, gt);
+
+    ImportPropertyMap properties;
+
+    this->PopulateIncludeDirectoriesInterface(
+      te, cmGeneratorExpression::InstallInterface, properties, missingTargets);
+    this->PopulateSourcesInterface(te, cmGeneratorExpression::InstallInterface,
+                                   properties, missingTargets);
+    this->PopulateInterfaceProperty("INTERFACE_SYSTEM_INCLUDE_DIRECTORIES", gt,
+                                    cmGeneratorExpression::InstallInterface,
+                                    properties, missingTargets);
+    this->PopulateInterfaceProperty("INTERFACE_COMPILE_DEFINITIONS", gt,
+                                    cmGeneratorExpression::InstallInterface,
+                                    properties, missingTargets);
+    this->PopulateInterfaceProperty("INTERFACE_COMPILE_OPTIONS", gt,
+                                    cmGeneratorExpression::InstallInterface,
+                                    properties, missingTargets);
+    this->PopulateInterfaceProperty("INTERFACE_AUTOUIC_OPTIONS", gt,
+                                    cmGeneratorExpression::InstallInterface,
+                                    properties, missingTargets);
+    this->PopulateInterfaceProperty("INTERFACE_COMPILE_FEATURES", gt,
+                                    cmGeneratorExpression::InstallInterface,
+                                    properties, missingTargets);
+
+    const bool newCMP0022Behavior =
+      gt->GetPolicyStatusCMP0022() != cmPolicies::WARN &&
+      gt->GetPolicyStatusCMP0022() != cmPolicies::OLD;
+    if (newCMP0022Behavior) {
+      this->PopulateInterfaceLinkLibrariesProperty(
+        gt, cmGeneratorExpression::InstallInterface, properties,
+        missingTargets);
+    }
+
+    this->PopulateInterfaceProperty("INTERFACE_POSITION_INDEPENDENT_CODE", gt,
+                                    properties);
+
+    this->PopulateCompatibleInterfaceProperties(gt, properties);
+
+    this->GenerateInterfaceProperties(gt, os, properties);
+
+    os << "    }";
+    if (i != allTargets.end() - 1)
+      os << ",";
+    os << "\n";
+  }
+  os << "  }\n";
+
+  this->LoadConfigFiles(os);
+
+  this->CleanupTemporaryVariables(os);
+  this->GenerateImportedFileCheckLoop(os);
+
+  bool result = true;
+  // Generate an import file for each configuration.
+  // Don't do this if we only export INTERFACE_LIBRARY targets.
+  if (requiresConfigFiles) {
+    for (std::string const& c : this->Configurations) {
+      if (!this->GenerateImportFileConfig(c, missingTargets)) {
+        result = false;
+      }
+    }
+  }
+
+  this->GenerateMissingTargetsCheckCode(os, missingTargets);
+
+  os << "}\n";
+
+  return result;
+}
+
+void cmExportInstallJSONGenerator::GenerateImportHeaderCode(
+  std::ostream& os, const std::string&)
+{
+  std::string installDir = this->IEGen->GetDestination();
+  for (cmTargetExport* te : *this->IEGen->GetExportSet()->GetTargetExports()) {
+    // Collect import properties for this target.
+    if (te->Target->GetType() == cmStateEnums::INTERFACE_LIBRARY) {
+      continue;
+    }
+    std::string dest;
+    if (te->LibraryGenerator) {
+      dest = te->LibraryGenerator->GetDestination("");
+    }
+    if (te->ArchiveGenerator) {
+      dest = te->ArchiveGenerator->GetDestination("");
+    }
+    te->Target->Target->SetProperty("__dest", dest.c_str());
+  }
+}
+
+void cmExportInstallJSONGenerator::GenerateImportFooterCode(std::ostream&)
+{
+}
+
+void cmExportInstallJSONGenerator::GenerateImportTargetCode(
+  std::ostream& os, const cmGeneratorTarget* target)
+{
+  std::string targetName = this->Namespace;
+  targetName += target->GetExportName();
+  os << "    \"" << targetName << "\": {\n";
+  os << "      \"output\": \"";
+  os << target->Target->GetProperty("__dest") << "/";
+  std::string config;
+  if (!this->Configurations.empty()) {
+    config = this->Configurations[0];
+  }
+  os << target->GetFullName(config) << "\",\n";
+}
+
+void cmExportInstallJSONGenerator::GenerateExpectedTargetsCode(
+  std::ostream&, const std::string&)
+{
+}
+
+void cmExportInstallJSONGenerator::GenerateImportPropertyCode(
+  std::ostream&, const std::string&, cmGeneratorTarget const*,
+  ImportPropertyMap const&)
+{
+}
+
+void cmExportInstallJSONGenerator::GenerateMissingTargetsCheckCode(
+  std::ostream&, const std::vector<std::string>&)
+{
+}
+
+void cmExportInstallJSONGenerator::GenerateInterfaceProperties(
+  cmGeneratorTarget const* target, std::ostream& os,
+  const ImportPropertyMap& properties)
+{
+  std::string config;
+  if (!this->Configurations.empty()) {
+    config = this->Configurations[0];
+  }
+  cmExportBuildJSONGenerator::GenerateInterfaceProperties(
+    target, os, properties, cmExportBuildJSONGenerator::INSTALL, config);
+}
+
+void cmExportInstallJSONGenerator::LoadConfigFiles(std::ostream&)
+{
+}
+
+void cmExportInstallJSONGenerator::GenerateImportPrefix(std::ostream&)
+{
+}
+
+void cmExportInstallJSONGenerator::GenerateRequiredCMakeVersion(
+  std::ostream&, const char*)
+{
+}
+
+void cmExportInstallJSONGenerator::CleanupTemporaryVariables(
+  std::ostream&)
+{
+}
+
+void cmExportInstallJSONGenerator::GenerateImportedFileCheckLoop(
+  std::ostream&)
+{
+}
+
+void cmExportInstallJSONGenerator::GenerateImportedFileChecksCode(
+  std::ostream&, cmGeneratorTarget*, ImportPropertyMap const&,
+  const std::set<std::string>&)
+{
+}
+
+bool cmExportInstallJSONGenerator::GenerateImportFileConfig(
+  const std::string&, std::vector<std::string>&)
+{
+  return true;
+}
diff --git a/Source/cmExportInstallJSONGenerator.h b/Source/cmExportInstallJSONGenerator.h
new file mode 100644
index 0000000..5f0568f
--- /dev/null
+++ b/Source/cmExportInstallJSONGenerator.h
@@ -0,0 +1,72 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmExportInstallJSONGenerator_h
+#define cmExportInstallJSONGenerator_h
+
+#include "cmConfigure.h" // IWYU pragma: keep
+
+#include <iosfwd>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "cmExportFileGenerator.h"
+#include "cmExportInstallFileGenerator.h"
+
+class cmGeneratorTarget;
+class cmInstallExportGenerator;
+
+/** \class cmExportInstallJSONGenerator
+ * \brief Generate a file exporting targets from an install tree.
+ *
+ * cmExportInstallJSONGenerator generates files exporting targets from
+ * install an installation tree.  The files are placed in a temporary
+ * location for installation by cmInstallExportGenerator.  The file format
+ * is for the ndk build system and is a makefile fragment specifing prebuilt
+ * libraries to the ndk build system.
+ *
+ * This is used to implement the INSTALL(EXPORT_JSON) command.
+ */
+class cmExportInstallJSONGenerator : public cmExportInstallFileGenerator
+{
+public:
+  /** Construct with the export installer that will install the
+      files.  */
+  cmExportInstallJSONGenerator(cmInstallExportGenerator* iegen);
+
+protected:
+  // Implement virtual methods from the superclass.
+  bool GenerateMainFile(std::ostream& os) override;
+  void GeneratePolicyHeaderCode(std::ostream&) override {}
+  void GeneratePolicyFooterCode(std::ostream&) override {}
+  void GenerateImportHeaderCode(std::ostream& os,
+                                const std::string& config = "") override;
+  void GenerateImportFooterCode(std::ostream& os) override;
+  void GenerateImportTargetCode(std::ostream& os,
+                                const cmGeneratorTarget* target) override;
+  void GenerateExpectedTargetsCode(
+    std::ostream& os, const std::string& expectedTargets) override;
+  void GenerateImportPropertyCode(
+    std::ostream& os, const std::string& config,
+    cmGeneratorTarget const* target,
+    ImportPropertyMap const& properties) override;
+  void GenerateMissingTargetsCheckCode(
+    std::ostream& os, const std::vector<std::string>& missingTargets) override;
+  void GenerateInterfaceProperties(
+    cmGeneratorTarget const* target, std::ostream& os,
+    const ImportPropertyMap& properties) override;
+  void GenerateImportPrefix(std::ostream& os) override;
+  void LoadConfigFiles(std::ostream&) override;
+  void GenerateRequiredCMakeVersion(std::ostream& os,
+                                    const char* versionString) override;
+  void CleanupTemporaryVariables(std::ostream&) override;
+  void GenerateImportedFileCheckLoop(std::ostream& os) override;
+  void GenerateImportedFileChecksCode(
+    std::ostream& os, cmGeneratorTarget* target,
+    ImportPropertyMap const& properties,
+    const std::set<std::string>& importedLocations) override;
+  bool GenerateImportFileConfig(const std::string& config,
+                                std::vector<std::string>&) override;
+};
+
+#endif
diff --git a/Source/cmInstallCommand.cxx b/Source/cmInstallCommand.cxx
index 685fc67..9f2aac5 100644
--- a/Source/cmInstallCommand.cxx
+++ b/Source/cmInstallCommand.cxx
@@ -102,6 +102,9 @@
   if (mode == "EXPORT_ANDROID_MK") {
     return this->HandleExportAndroidMKMode(args);
   }
+  if (mode == "EXPORT_JSON") {
+    return this->HandleExportJSONMode(args);
+  }
 
   // Unknown mode.
   std::string e = "called with unknown mode ";
@@ -1247,7 +1250,7 @@
     exportSet, ica.GetDestination().c_str(), ica.GetPermissions().c_str(),
     ica.GetConfigurations(), ica.GetComponent().c_str(), message,
     ica.GetExcludeFromAll(), fname.c_str(), name_space.GetCString(),
-    exportOld.IsEnabled(), true);
+    exportOld.IsEnabled(), true, false);
   this->Makefile->AddInstallGenerator(exportGenerator);
 
   return true;
@@ -1258,6 +1261,112 @@
 #endif
 }
 
+bool cmInstallCommand::HandleExportJSONMode(
+  std::vector<std::string> const& args)
+{
+#ifdef CMAKE_BUILD_WITH_CMAKE
+  // This is the EXPORT mode.
+  cmInstallCommandArguments ica(this->DefaultComponentName);
+  cmCAString exp(&ica.Parser, "EXPORT_JSON");
+  cmCAString name_space(&ica.Parser, "NAMESPACE", &ica.ArgumentGroup);
+  cmCAEnabler exportOld(&ica.Parser, "EXPORT_LINK_INTERFACE_LIBRARIES",
+                        &ica.ArgumentGroup);
+  cmCAString filename(&ica.Parser, "FILE", &ica.ArgumentGroup);
+  exp.Follows(nullptr);
+
+  ica.ArgumentGroup.Follows(&exp);
+  std::vector<std::string> unknownArgs;
+  ica.Parse(&args, &unknownArgs);
+
+  if (!unknownArgs.empty()) {
+    // Unknown argument.
+    std::ostringstream e;
+    e << args[0] << " given unknown argument \"" << unknownArgs[0] << "\".";
+    this->SetError(e.str());
+    return false;
+  }
+
+  if (!ica.Finalize()) {
+    return false;
+  }
+
+  // Make sure there is a destination.
+  if (ica.GetDestination().empty()) {
+    // A destination is required.
+    std::ostringstream e;
+    e << args[0] << " given no DESTINATION!";
+    this->SetError(e.str());
+    return false;
+  }
+
+  // Check the file name.
+  std::string fname = filename.GetString();
+  if (fname.find_first_of(":/\\") != std::string::npos) {
+    std::ostringstream e;
+    e << args[0] << " given invalid export file name \"" << fname << "\".  "
+      << "The FILE argument may not contain a path.  "
+      << "Specify the path in the DESTINATION argument.";
+    this->SetError(e.str());
+    return false;
+  }
+
+  // Check the file extension.
+  if (!fname.empty() &&
+      cmSystemTools::GetFilenameLastExtension(fname) != ".json") {
+    std::ostringstream e;
+    e << args[0] << " given invalid export file name \"" << fname << "\".  "
+      << "The FILE argument must specify a name ending in \".json\".";
+    this->SetError(e.str());
+    return false;
+  }
+  if (fname.find_first_of(":/\\") != std::string::npos) {
+    std::ostringstream e;
+    e << args[0] << " given export name \"" << exp.GetString() << "\".  "
+      << "This name cannot be safely converted to a file name.  "
+      << "Specify a different export name or use the FILE option to set "
+      << "a file name explicitly.";
+    this->SetError(e.str());
+    return false;
+  }
+
+  // Construct the file name.
+  if (fname.empty()) {
+    fname = exp.GetString();
+    fname += ".json";
+
+    if (fname.find_first_of(":/\\") != std::string::npos) {
+      std::ostringstream e;
+      e << args[0] << " given export name \"" << exp.GetString() << "\".  "
+        << "This name cannot be safely converted to a file name.  "
+        << "Specify a different export name or use the FILE option to set "
+        << "a file name explicitly.";
+      this->SetError(e.str());
+      return false;
+    }
+  }
+
+  cmExportSet* exportSet =
+    this->Makefile->GetGlobalGenerator()->GetExportSets()[exp.GetString()];
+
+  cmInstallGenerator::MessageLevel message =
+    cmInstallGenerator::SelectMessageLevel(this->Makefile);
+
+  // Create the export install generator.
+  cmInstallExportGenerator* exportGenerator = new cmInstallExportGenerator(
+    exportSet, ica.GetDestination().c_str(), ica.GetPermissions().c_str(),
+    ica.GetConfigurations(), ica.GetComponent().c_str(), message,
+    ica.GetExcludeFromAll(), fname.c_str(), name_space.GetCString(),
+    exportOld.IsEnabled(), false, true);
+  this->Makefile->AddInstallGenerator(exportGenerator);
+
+  return true;
+#else
+  static_cast<void>(args);
+  this->SetError("EXPORT_JSON not supported in bootstrap cmake");
+  return false;
+#endif
+}
+
 bool cmInstallCommand::HandleExportMode(std::vector<std::string> const& args)
 {
   // This is the EXPORT mode.
@@ -1361,7 +1470,7 @@
     exportSet, ica.GetDestination().c_str(), ica.GetPermissions().c_str(),
     ica.GetConfigurations(), ica.GetComponent().c_str(), message,
     ica.GetExcludeFromAll(), fname.c_str(), name_space.GetCString(),
-    exportOld.IsEnabled(), false);
+    exportOld.IsEnabled(), false, false);
   this->Makefile->AddInstallGenerator(exportGenerator);
 
   return true;
diff --git a/Source/cmInstallCommand.h b/Source/cmInstallCommand.h
index 8bd0159..b42a291 100644
--- a/Source/cmInstallCommand.h
+++ b/Source/cmInstallCommand.h
@@ -40,6 +40,7 @@
   bool HandleDirectoryMode(std::vector<std::string> const& args);
   bool HandleExportMode(std::vector<std::string> const& args);
   bool HandleExportAndroidMKMode(std::vector<std::string> const& args);
+  bool HandleExportJSONMode(std::vector<std::string> const& args);
   bool MakeFilesFullPath(const char* modeName,
                          const std::vector<std::string>& relFiles,
                          std::vector<std::string>& absFiles);
diff --git a/Source/cmInstallExportGenerator.cxx b/Source/cmInstallExportGenerator.cxx
index fdd231c..d8c778f 100644
--- a/Source/cmInstallExportGenerator.cxx
+++ b/Source/cmInstallExportGenerator.cxx
@@ -10,6 +10,9 @@
 #include "cmExportInstallAndroidMKGenerator.h"
 #endif
 #include "cmExportInstallFileGenerator.h"
+#ifdef CMAKE_BUILD_WITH_CMAKE
+#include "cmExportInstallJSONGenerator.h"
+#endif
 #include "cmExportSet.h"
 #include "cmInstallType.h"
 #include "cmLocalGenerator.h"
@@ -20,7 +23,8 @@
   cmExportSet* exportSet, const char* destination,
   const char* file_permissions, std::vector<std::string> const& configurations,
   const char* component, MessageLevel message, bool exclude_from_all,
-  const char* filename, const char* name_space, bool exportOld, bool android)
+  const char* filename, const char* name_space, bool exportOld, bool android,
+  bool json)
   : cmInstallGenerator(destination, configurations, component, message,
                        exclude_from_all)
   , ExportSet(exportSet)
@@ -34,6 +38,10 @@
 #ifdef CMAKE_BUILD_WITH_CMAKE
     this->EFGen = new cmExportInstallAndroidMKGenerator(this);
 #endif
+  } else if (json) {
+#ifdef CMAKE_BUILD_WITH_CMAKE
+    this->EFGen = new cmExportInstallJSONGenerator(this);
+#endif
   } else {
     this->EFGen = new cmExportInstallFileGenerator(this);
   }
diff --git a/Source/cmInstallExportGenerator.h b/Source/cmInstallExportGenerator.h
index d23cf06..2d22fec 100644
--- a/Source/cmInstallExportGenerator.h
+++ b/Source/cmInstallExportGenerator.h
@@ -29,7 +29,7 @@
                            const char* component, MessageLevel message,
                            bool exclude_from_all, const char* filename,
                            const char* name_space, bool exportOld,
-                           bool android);
+                           bool android, bool json);
   ~cmInstallExportGenerator() override;
 
   cmExportSet* GetExportSet() { return this->ExportSet; }
diff --git a/Tests/RunCMake/JSON/CMakeLists.txt b/Tests/RunCMake/JSON/CMakeLists.txt
new file mode 100644
index 0000000..576787a
--- /dev/null
+++ b/Tests/RunCMake/JSON/CMakeLists.txt
@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.5)
+project(${RunCMake_TEST} NONE) # or languages needed
+include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/JSON/JSON-check.cmake b/Tests/RunCMake/JSON/JSON-check.cmake
new file mode 100644
index 0000000..ae723a4
--- /dev/null
+++ b/Tests/RunCMake/JSON/JSON-check.cmake
@@ -0,0 +1,30 @@
+# This file does a regex file compare on the generated
+# Android.mk files from the AndroidMK test
+
+macro(compare_file_to_expected file expected_file)
+  file(READ "${file}" JSON)
+  # clean up new lines
+  string(REGEX REPLACE "\r\n" "\n" JSON "${JSON}")
+  string(REGEX REPLACE "\n+$" "" JSON "${JSON}")
+  # read in the expected regex file
+  file(READ "${expected_file}" expected)
+  # clean up new lines
+  string(REGEX REPLACE "\r\n" "\n" expected "${expected}")
+  string(REGEX REPLACE "\n+$" "" expected "${expected}")
+  # compare the file to the expected regex and if there is not a match
+  # put an error message in RunCMake_TEST_FAILED
+  if(NOT "${JSON}" MATCHES "${expected}")
+    set(RunCMake_TEST_FAILED
+      "${file} does not match ${expected_file}:
+
+JSON contents = [\n${JSON}\n]
+Expected = [\n${expected}\n]")
+  endif()
+endmacro()
+
+compare_file_to_expected(
+  "${RunCMake_BINARY_DIR}/JSON-build/myexp.json"
+  "${RunCMake_TEST_SOURCE_DIR}/expectedBuildJSON.txt")
+compare_file_to_expected(
+  "${RunCMake_BINARY_DIR}/JSON-build/CMakeFiles/Export/share/myexp.json"
+  "${RunCMake_TEST_SOURCE_DIR}/expectedInstallJSON.txt")
diff --git a/Tests/RunCMake/JSON/JSON.cmake b/Tests/RunCMake/JSON/JSON.cmake
new file mode 100644
index 0000000..4e3b5c4
--- /dev/null
+++ b/Tests/RunCMake/JSON/JSON.cmake
@@ -0,0 +1,11 @@
+project(build)
+set(CMAKE_BUILD_TYPE Debug)
+add_library(foo foo.cxx)
+add_library(car foo.cxx)
+add_library(bar bar.c)
+add_library(dog  foo.cxx)
+target_link_libraries(foo car bar dog debug -lm)
+export(TARGETS bar dog car foo JSON
+  ${build_BINARY_DIR}/myexp.json)
+install(TARGETS bar dog car foo DESTINATION lib EXPORT myexp)
+install(EXPORT_JSON myexp DESTINATION share)
diff --git a/Tests/RunCMake/JSON/RunCMakeTest.cmake b/Tests/RunCMake/JSON/RunCMakeTest.cmake
new file mode 100644
index 0000000..2156eee
--- /dev/null
+++ b/Tests/RunCMake/JSON/RunCMakeTest.cmake
@@ -0,0 +1,2 @@
+include(RunCMake)
+run_cmake(JSON)
diff --git a/Tests/RunCMake/JSON/bar.c b/Tests/RunCMake/JSON/bar.c
new file mode 100644
index 0000000..e1f4df6
--- /dev/null
+++ b/Tests/RunCMake/JSON/bar.c
@@ -0,0 +1,3 @@
+void bar(void)
+{
+}
diff --git a/Tests/RunCMake/JSON/expectedBuildJSON.txt b/Tests/RunCMake/JSON/expectedBuildJSON.txt
new file mode 100644
index 0000000..bbf67a5
--- /dev/null
+++ b/Tests/RunCMake/JSON/expectedBuildJSON.txt
@@ -0,0 +1,26 @@
+LOCAL_PATH.*call my-dir.*
+include.*CLEAR_VARS.*
+LOCAL_MODULE.*bar
+LOCAL_SRC_FILES.*bar.*
+include.*PREBUILT_STATIC_LIBRARY.*
+.*
+include.*CLEAR_VARS.*
+LOCAL_MODULE.*dog
+LOCAL_SRC_FILES.*.*dog.*
+LOCAL_HAS_CPP := true
+include.*PREBUILT_STATIC_LIBRARY.*
+.*
+include.*CLEAR_VARS.*
+LOCAL_MODULE.*car
+LOCAL_SRC_FILES.*.*car.*
+LOCAL_HAS_CPP := true
+include.*PREBUILT_STATIC_LIBRARY.*
+.*
+include.*CLEAR_VARS.*
+LOCAL_MODULE.*foo
+LOCAL_SRC_FILES.*.*foo.*
+LOCAL_CPP_FEATURES.*rtti exceptions
+LOCAL_STATIC_LIBRARIES.*car bar dog
+LOCAL_EXPORT_LDLIBS := -lm
+LOCAL_HAS_CPP := true
+include.*PREBUILT_STATIC_LIBRARY.*
diff --git a/Tests/RunCMake/JSON/expectedInstallJSON.txt b/Tests/RunCMake/JSON/expectedInstallJSON.txt
new file mode 100644
index 0000000..3515fb9
--- /dev/null
+++ b/Tests/RunCMake/JSON/expectedInstallJSON.txt
@@ -0,0 +1,28 @@
+LOCAL_PATH.*call my-dir.*
+_IMPORT_PREFIX.*LOCAL_PATH./../..
+
+include.*CLEAR_VARS.*
+LOCAL_MODULE.*bar
+LOCAL_SRC_FILES.*_IMPORT_PREFIX./lib.*bar.*
+include.*PREBUILT_STATIC_LIBRARY.*
+
+include.*CLEAR_VARS.
+LOCAL_MODULE.*dog
+LOCAL_SRC_FILES.*_IMPORT_PREFIX./lib.*dog.*
+LOCAL_HAS_CPP := true
+include.*PREBUILT_STATIC_LIBRARY.*
+
+include.*CLEAR_VARS.*
+LOCAL_MODULE.*car
+LOCAL_SRC_FILES.*_IMPORT_PREFIX./lib.*car.*
+LOCAL_HAS_CPP := true
+include.*PREBUILT_STATIC_LIBRARY.*
+
+include.*CLEAR_VARS.*
+LOCAL_MODULE.*foo
+LOCAL_SRC_FILES.*_IMPORT_PREFIX\)/lib.*foo.*
+LOCAL_CPP_FEATURES.*rtti exceptions
+LOCAL_STATIC_LIBRARIES.*car bar dog
+LOCAL_EXPORT_LDLIBS := -lm
+LOCAL_HAS_CPP := true
+include.*PREBUILT_STATIC_LIBRARY.*
diff --git a/Tests/RunCMake/JSON/foo.cxx b/Tests/RunCMake/JSON/foo.cxx
new file mode 100644
index 0000000..3695dc9
--- /dev/null
+++ b/Tests/RunCMake/JSON/foo.cxx
@@ -0,0 +1,3 @@
+void foo()
+{
+}