瀏覽代碼

Add support for creating prebuilt Android.mk files

Add options to the `install()` and `export()` commands to export the
targets we build into Android.mk files that reference them as prebuilt
libraries with associated usage requirements (compile definitions,
include directories, link libraries).  This will allow CMake-built
projects to be imported into projects using the Android NDK build
system.

Closes: #15562
Bill Hoffman 9 年之前
父節點
當前提交
42ce9f1e71

+ 15 - 0
Help/command/export.rst

@@ -55,3 +55,18 @@ build tree. In some cases, for example for packaging and for system
 wide installations, it is not desirable to write the user package
 registry. If the :variable:`CMAKE_EXPORT_NO_PACKAGE_REGISTRY` variable
 is enabled, the ``export(PACKAGE)`` command will do nothing.
+
+::
+
+  export(TARGETS [target1 [target2 [...]]]  [ANDROID_MK <filename>])
+
+This signature exports cmake built targets to the android ndk build system
+by creating an Android.mk file that references the prebuilt targets. The
+Android NDK supports the use of prebuilt libraries, both static and shared.
+This allows cmake to build the libraries of a project and make them available
+to an ndk build system complete with transitive dependencies, include flags
+and defines required to use the libraries. The signature takes a list of
+targets and puts them in the Android.mk file specified by the ``<filename>``
+given. This signature can only be used if policy CMP0022 is NEW for all
+targets given. A error will be issued if that policy is set to OLD for one
+of the targets.

+ 12 - 2
Help/command/install.rst

@@ -314,7 +314,8 @@ Installing Exports
 ::
 
   install(EXPORT <export-name> DESTINATION <dir>
-          [NAMESPACE <namespace>] [FILE <name>.cmake]
+          [NAMESPACE <namespace>] [[FILE <name>.cmake]|
+          [EXPORT_ANDROID_MK <name>.mk]]
           [PERMISSIONS permissions...]
           [CONFIGURATIONS [Debug|Release|...]]
           [EXPORT_LINK_INTERFACE_LIBRARIES]
@@ -342,6 +343,13 @@ specified that does not match that given to the targets associated with
 included in the export but a target to which it links is not included
 the behavior is unspecified.
 
+In additon to cmake language files, the ``EXPORT_ANDROID_MK`` option maybe
+used to specifiy an export to the android ndk build system.  The Android
+NDK supports the use of prebuilt libraries, both static and shared. This
+allows cmake to build the libraries of a project and make them available
+to an ndk build system complete with transitive dependencies, include flags
+and defines required to use the libraries.
+
 The ``EXPORT`` form is useful to help outside projects use targets built
 and installed by the current project.  For example, the code
 
@@ -349,9 +357,11 @@ and installed by the current project.  For example, the code
 
   install(TARGETS myexe EXPORT myproj DESTINATION bin)
   install(EXPORT myproj NAMESPACE mp_ DESTINATION lib/myproj)
+  install(EXPORT_ANDROID_MK myexp DESTINATION share/ndk-modules)
 
 will install the executable myexe to ``<prefix>/bin`` and code to import
-it in the file ``<prefix>/lib/myproj/myproj.cmake``.  An outside project
+it in the file ``<prefix>/lib/myproj/myproj.cmake`` and
+``<prefix>/lib/share/ndk-modules/Android.mk``.  An outside project
 may load this file with the include command and reference the ``myexe``
 executable from the installation tree using the imported target name
 ``mp_myexe`` as if the target were built in its own tree.

+ 10 - 0
Help/release/dev/add_androidmk_generator.rst

@@ -0,0 +1,10 @@
+add_androidmk_generator
+-----------------------
+
+* The :command:`install` command gained an ``EXPORT_ANDROID_MK``
+  subcommand to install ``Android.mk`` files referencing installed
+  libraries as prebuilts for the Android NDK build system.
+
+* The :command:`export` command gained an ``ANDROID_MK`` option
+  to generate ``Android.mk`` files referencing CMake-built
+  libraries as prebuilts for the Android NDK build system.

+ 4 - 0
Source/CMakeLists.txt

@@ -221,10 +221,14 @@ set(SRCS
   cmExprLexer.cxx
   cmExprParser.cxx
   cmExprParserHelper.cxx
+  cmExportBuildAndroidMKGenerator.h
+  cmExportBuildAndroidMKGenerator.cxx
   cmExportBuildFileGenerator.h
   cmExportBuildFileGenerator.cxx
   cmExportFileGenerator.h
   cmExportFileGenerator.cxx
+  cmExportInstallAndroidMKGenerator.h
+  cmExportInstallAndroidMKGenerator.cxx
   cmExportInstallFileGenerator.h
   cmExportInstallFileGenerator.cxx
   cmExportTryCompileFileGenerator.h

+ 193 - 0
Source/cmExportBuildAndroidMKGenerator.cxx

@@ -0,0 +1,193 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+
+  Distributed under the OSI-approved BSD License (the "License");
+  see accompanying file Copyright.txt for details.
+
+  This software is distributed WITHOUT ANY WARRANTY; without even the
+  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the License for more information.
+============================================================================*/
+#include "cmExportBuildAndroidMKGenerator.h"
+
+#include "cmExportSet.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobalGenerator.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmTargetExport.h"
+
+cmExportBuildAndroidMKGenerator::cmExportBuildAndroidMKGenerator()
+{
+  this->LG = CM_NULLPTR;
+  this->ExportSet = CM_NULLPTR;
+}
+
+void cmExportBuildAndroidMKGenerator::GenerateImportHeaderCode(
+  std::ostream& os, const std::string&)
+{
+  os << "LOCAL_PATH := $(call my-dir)\n\n";
+}
+
+void cmExportBuildAndroidMKGenerator::GenerateImportFooterCode(std::ostream&)
+{
+}
+
+void cmExportBuildAndroidMKGenerator::GenerateExpectedTargetsCode(
+  std::ostream&, const std::string&)
+{
+}
+
+void cmExportBuildAndroidMKGenerator::GenerateImportTargetCode(
+  std::ostream& os, const cmGeneratorTarget* target)
+{
+  std::string targetName = this->Namespace;
+  targetName += target->GetExportName();
+  os << "include $(CLEAR_VARS)\n";
+  os << "LOCAL_MODULE := ";
+  os << targetName << "\n";
+  os << "LOCAL_SRC_FILES := ";
+  std::string path = target->GetLocalGenerator()->ConvertToOutputFormat(
+    target->GetFullPath(), cmOutputConverter::MAKERULE);
+  os << path << "\n";
+}
+
+void cmExportBuildAndroidMKGenerator::GenerateImportPropertyCode(
+  std::ostream&, const std::string&, cmGeneratorTarget const*,
+  ImportPropertyMap const&)
+{
+}
+
+void cmExportBuildAndroidMKGenerator::GenerateMissingTargetsCheckCode(
+  std::ostream&, const std::vector<std::string>&)
+{
+}
+
+void cmExportBuildAndroidMKGenerator::GenerateInterfaceProperties(
+  const cmGeneratorTarget* target, std::ostream& os,
+  const ImportPropertyMap& properties)
+{
+  std::string config = "";
+  if (this->Configurations.size()) {
+    config = this->Configurations[0];
+  }
+  cmExportBuildAndroidMKGenerator::GenerateInterfaceProperties(
+    target, os, properties, cmExportBuildAndroidMKGenerator::BUILD, config);
+}
+
+void cmExportBuildAndroidMKGenerator::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 == cmExportBuildAndroidMKGenerator::BUILD) {
+      w << "export(TARGETS ... ANDROID_MK) called with policy CMP0022";
+    } else {
+      w << "install( EXPORT_ANDROID_MK ...) 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()) {
+    os << "LOCAL_CPP_FEATURES := rtti exceptions\n";
+    for (ImportPropertyMap::const_iterator pi = properties.begin();
+         pi != properties.end(); ++pi) {
+      if (pi->first == "INTERFACE_COMPILE_OPTIONS") {
+        os << "LOCAL_CPP_FEATURES += ";
+        os << (pi->second) << "\n";
+      } else if (pi->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(pi->second, libraries);
+        std::string staticLibs;
+        std::string sharedLibs;
+        std::string ldlibs;
+        for (std::vector<std::string>::iterator i = libraries.begin();
+             i != libraries.end(); ++i) {
+          cmGeneratorTarget* gt =
+            target->GetLocalGenerator()->FindGeneratorTargetToUse(*i);
+          if (gt) {
+
+            if (gt->GetType() == cmState::SHARED_LIBRARY ||
+                gt->GetType() == cmState::MODULE_LIBRARY) {
+              sharedLibs += " " + *i;
+            } else {
+              staticLibs += " " + *i;
+            }
+          } else {
+            // evaluate any generator expressions with the current
+            // build type of the makefile
+            cmGeneratorExpression ge;
+            CM_AUTO_PTR<cmCompiledGeneratorExpression> cge = ge.Parse(*i);
+            std::string evaluated =
+              cge->Evaluate(target->GetLocalGenerator(), config);
+            bool relpath = false;
+            if (type == cmExportBuildAndroidMKGenerator::INSTALL) {
+              relpath = i->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 += " " + evaluated;
+              // if it is not a path and does not have a -l then add -l
+            } else if (!evaluated.empty()) {
+              ldlibs += " -l" + evaluated;
+            }
+          }
+        }
+        if (!sharedLibs.empty()) {
+          os << "LOCAL_SHARED_LIBRARIES :=" << sharedLibs << "\n";
+        }
+        if (!staticLibs.empty()) {
+          os << "LOCAL_STATIC_LIBRARIES :=" << staticLibs << "\n";
+        }
+        if (!ldlibs.empty()) {
+          os << "LOCAL_EXPORT_LDLIBS :=" << ldlibs << "\n";
+        }
+      } else if (pi->first == "INTERFACE_INCLUDE_DIRECTORIES") {
+        std::string includes = pi->second;
+        std::vector<std::string> includeList;
+        cmSystemTools::ExpandListArgument(includes, includeList);
+        os << "LOCAL_EXPORT_C_INCLUDES := ";
+        std::string end;
+        for (std::vector<std::string>::iterator i = includeList.begin();
+             i != includeList.end(); ++i) {
+          os << end << *i;
+          end = "\\\n";
+        }
+        os << "\n";
+      } else {
+        os << "# " << pi->first << " " << (pi->second) << "\n";
+      }
+    }
+  }
+  switch (target->GetType()) {
+    case cmState::SHARED_LIBRARY:
+    case cmState::MODULE_LIBRARY:
+      os << "include $(PREBUILT_SHARED_LIBRARY)\n";
+      break;
+    case cmState::STATIC_LIBRARY:
+      os << "include $(PREBUILT_STATIC_LIBRARY)\n";
+      break;
+    case cmState::EXECUTABLE:
+    case cmState::UTILITY:
+    case cmState::OBJECT_LIBRARY:
+    case cmState::GLOBAL_TARGET:
+    case cmState::INTERFACE_LIBRARY:
+    case cmState::UNKNOWN_LIBRARY:
+      break;
+  }
+  os << "\n";
+}

+ 68 - 0
Source/cmExportBuildAndroidMKGenerator.h

@@ -0,0 +1,68 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+
+  Distributed under the OSI-approved BSD License (the "License");
+  see accompanying file Copyright.txt for details.
+
+  This software is distributed WITHOUT ANY WARRANTY; without even the
+  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the License for more information.
+============================================================================*/
+#ifndef cmExportBuildAndroidMKGenerator_h
+#define cmExportBuildAndroidMKGenerator_h
+
+#include "cmExportBuildFileGenerator.h"
+#include "cmListFileCache.h"
+
+class cmExportSet;
+
+/** \class cmExportBuildAndroidMKGenerator
+ * \brief Generate a file exporting targets from a build tree.
+ *
+ * cmExportBuildAndroidMKGenerator generates a file exporting targets from
+ * a build tree.  This exports the targets to the Android ndk build tool
+ * makefile format for prebuilt libraries.
+ *
+ * This is used to implement the EXPORT() command.
+ */
+class cmExportBuildAndroidMKGenerator : public cmExportBuildFileGenerator
+{
+public:
+  cmExportBuildAndroidMKGenerator();
+  // this is so cmExportInstallAndroidMKGenerator 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);
+
+protected:
+  // Implement virtual methods from the superclass.
+  virtual void GeneratePolicyHeaderCode(std::ostream&) {}
+  virtual void GeneratePolicyFooterCode(std::ostream&) {}
+  virtual void GenerateImportHeaderCode(std::ostream& os,
+                                        const std::string& config = "");
+  virtual void GenerateImportFooterCode(std::ostream& os);
+  virtual void GenerateImportTargetCode(std::ostream& os,
+                                        const cmGeneratorTarget* target);
+  virtual void GenerateExpectedTargetsCode(std::ostream& os,
+                                           const std::string& expectedTargets);
+  virtual void GenerateImportPropertyCode(std::ostream& os,
+                                          const std::string& config,
+                                          cmGeneratorTarget const* target,
+                                          ImportPropertyMap const& properties);
+  virtual void GenerateMissingTargetsCheckCode(
+    std::ostream& os, const std::vector<std::string>& missingTargets);
+  virtual void GenerateInterfaceProperties(
+    cmGeneratorTarget const* target, std::ostream& os,
+    const ImportPropertyMap& properties);
+};
+
+#endif

+ 15 - 3
Source/cmExportCommand.cxx

@@ -18,6 +18,7 @@
 #include <cmsys/Encoding.hxx>
 #include <cmsys/RegularExpression.hxx>
 
+#include "cmExportBuildAndroidMKGenerator.h"
 #include "cmExportBuildFileGenerator.h"
 
 #if defined(__HAIKU__)
@@ -34,6 +35,7 @@ cmExportCommand::cmExportCommand()
   , Namespace(&Helper, "NAMESPACE", &ArgumentGroup)
   , Filename(&Helper, "FILE", &ArgumentGroup)
   , ExportOld(&Helper, "EXPORT_LINK_INTERFACE_LIBRARIES", &ArgumentGroup)
+  , AndroidMKFile(&Helper, "ANDROID_MK")
 {
   this->ExportSet = CM_NULLPTR;
 }
@@ -66,13 +68,18 @@ bool cmExportCommand::InitialPass(std::vector<std::string> const& args,
   }
 
   std::string fname;
-  if (!this->Filename.WasFound()) {
+  bool android = false;
+  if (this->AndroidMKFile.WasFound()) {
+    fname = this->AndroidMKFile.GetString();
+    android = true;
+  }
+  if (!this->Filename.WasFound() && fname.empty()) {
     if (args[0] != "EXPORT") {
       this->SetError("FILE <filename> option missing.");
       return false;
     }
     fname = this->ExportSetName.GetString() + ".cmake";
-  } else {
+  } else if (fname.empty()) {
     // Make sure the file has a .cmake extension.
     if (cmSystemTools::GetFilenameLastExtension(this->Filename.GetCString()) !=
         ".cmake") {
@@ -176,7 +183,12 @@ bool cmExportCommand::InitialPass(std::vector<std::string> const& args,
   }
 
   // Setup export file generation.
-  cmExportBuildFileGenerator* ebfg = new cmExportBuildFileGenerator;
+  cmExportBuildFileGenerator* ebfg = CM_NULLPTR;
+  if (android) {
+    ebfg = new cmExportBuildAndroidMKGenerator;
+  } else {
+    ebfg = new cmExportBuildFileGenerator;
+  }
   ebfg->SetExportFile(fname.c_str());
   ebfg->SetNamespace(this->Namespace.GetCString());
   ebfg->SetAppendMode(this->Append.IsEnabled());

+ 1 - 0
Source/cmExportCommand.h

@@ -54,6 +54,7 @@ private:
   cmCAString Namespace;
   cmCAString Filename;
   cmCAEnabler ExportOld;
+  cmCAString AndroidMKFile;
 
   cmExportSet* ExportSet;
 

+ 146 - 0
Source/cmExportInstallAndroidMKGenerator.cxx

@@ -0,0 +1,146 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+
+  Distributed under the OSI-approved BSD License (the "License");
+  see accompanying file Copyright.txt for details.
+
+  This software is distributed WITHOUT ANY WARRANTY; without even the
+  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the License for more information.
+============================================================================*/
+#include "cmExportInstallAndroidMKGenerator.h"
+
+#include "cmAlgorithms.h"
+#include "cmExportBuildAndroidMKGenerator.h"
+#include "cmExportSet.h"
+#include "cmExportSetMap.h"
+#include "cmGeneratedFileStream.h"
+#include "cmGeneratorTarget.h"
+#include "cmGlobalGenerator.h"
+#include "cmInstallExportGenerator.h"
+#include "cmInstallTargetGenerator.h"
+#include "cmLocalGenerator.h"
+#include "cmTargetExport.h"
+
+cmExportInstallAndroidMKGenerator::cmExportInstallAndroidMKGenerator(
+  cmInstallExportGenerator* iegen)
+  : cmExportInstallFileGenerator(iegen)
+{
+}
+
+void cmExportInstallAndroidMKGenerator::GenerateImportHeaderCode(
+  std::ostream& os, const std::string&)
+{
+  std::string installDir = this->IEGen->GetDestination();
+  os << "LOCAL_PATH := $(call my-dir)\n";
+  size_t numDotDot = cmSystemTools::CountChar(installDir.c_str(), '/');
+  numDotDot += (installDir.size() > 0) ? 1 : 0;
+  std::string path;
+  for (size_t n = 0; n < numDotDot; n++) {
+    path += "/..";
+  }
+  os << "_IMPORT_PREFIX := "
+     << "$(LOCAL_PATH)" << path << "\n\n";
+  for (std::vector<cmTargetExport*>::const_iterator tei =
+         this->IEGen->GetExportSet()->GetTargetExports()->begin();
+       tei != this->IEGen->GetExportSet()->GetTargetExports()->end(); ++tei) {
+    // Collect import properties for this target.
+    cmTargetExport const* te = *tei;
+    if (te->Target->GetType() == cmState::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 cmExportInstallAndroidMKGenerator::GenerateImportFooterCode(std::ostream&)
+{
+}
+
+void cmExportInstallAndroidMKGenerator::GenerateImportTargetCode(
+  std::ostream& os, const cmGeneratorTarget* target)
+{
+  std::string targetName = this->Namespace;
+  targetName += target->GetExportName();
+  os << "include $(CLEAR_VARS)\n";
+  os << "LOCAL_MODULE := ";
+  os << targetName << "\n";
+  os << "LOCAL_SRC_FILES := $(_IMPORT_PREFIX)/";
+  os << target->Target->GetProperty("__dest") << "/";
+  std::string config = "";
+  if (this->Configurations.size()) {
+    config = this->Configurations[0];
+  }
+  os << target->GetFullName(config) << "\n";
+}
+
+void cmExportInstallAndroidMKGenerator::GenerateExpectedTargetsCode(
+  std::ostream&, const std::string&)
+{
+}
+
+void cmExportInstallAndroidMKGenerator::GenerateImportPropertyCode(
+  std::ostream&, const std::string&, cmGeneratorTarget const*,
+  ImportPropertyMap const&)
+{
+}
+
+void cmExportInstallAndroidMKGenerator::GenerateMissingTargetsCheckCode(
+  std::ostream&, const std::vector<std::string>&)
+{
+}
+
+void cmExportInstallAndroidMKGenerator::GenerateInterfaceProperties(
+  cmGeneratorTarget const* target, std::ostream& os,
+  const ImportPropertyMap& properties)
+{
+  std::string config = "";
+  if (this->Configurations.size()) {
+    config = this->Configurations[0];
+  }
+  cmExportBuildAndroidMKGenerator::GenerateInterfaceProperties(
+    target, os, properties, cmExportBuildAndroidMKGenerator::INSTALL, config);
+}
+
+void cmExportInstallAndroidMKGenerator::LoadConfigFiles(std::ostream&)
+{
+}
+
+void cmExportInstallAndroidMKGenerator::GenerateImportPrefix(std::ostream&)
+{
+}
+
+void cmExportInstallAndroidMKGenerator::GenerateRequiredCMakeVersion(
+  std::ostream&, const char*)
+{
+}
+
+void cmExportInstallAndroidMKGenerator::CleanupTemporaryVariables(
+  std::ostream&)
+{
+}
+
+void cmExportInstallAndroidMKGenerator::GenerateImportedFileCheckLoop(
+  std::ostream&)
+{
+}
+
+void cmExportInstallAndroidMKGenerator::GenerateImportedFileChecksCode(
+  std::ostream&, cmGeneratorTarget*, ImportPropertyMap const&,
+  const std::set<std::string>&)
+{
+}
+
+bool cmExportInstallAndroidMKGenerator::GenerateImportFileConfig(
+  const std::string&, std::vector<std::string>&)
+{
+  return true;
+}

+ 72 - 0
Source/cmExportInstallAndroidMKGenerator.h

@@ -0,0 +1,72 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+
+  Distributed under the OSI-approved BSD License (the "License");
+  see accompanying file Copyright.txt for details.
+
+  This software is distributed WITHOUT ANY WARRANTY; without even the
+  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the License for more information.
+============================================================================*/
+#ifndef cmExportInstallAndroidMKGenerator_h
+#define cmExportInstallAndroidMKGenerator_h
+
+#include "cmExportInstallFileGenerator.h"
+
+class cmInstallExportGenerator;
+class cmInstallTargetGenerator;
+
+/** \class cmExportInstallAndroidMKGenerator
+ * \brief Generate a file exporting targets from an install tree.
+ *
+ * cmExportInstallAndroidMKGenerator 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_ANDROID_MK) command.
+ */
+class cmExportInstallAndroidMKGenerator : public cmExportInstallFileGenerator
+{
+public:
+  /** Construct with the export installer that will install the
+      files.  */
+  cmExportInstallAndroidMKGenerator(cmInstallExportGenerator* iegen);
+
+protected:
+  // Implement virtual methods from the superclass.
+  virtual void GeneratePolicyHeaderCode(std::ostream&) {}
+  virtual void GeneratePolicyFooterCode(std::ostream&) {}
+  virtual void GenerateImportHeaderCode(std::ostream& os,
+                                        const std::string& config = "");
+  virtual void GenerateImportFooterCode(std::ostream& os);
+  virtual void GenerateImportTargetCode(std::ostream& os,
+                                        const cmGeneratorTarget* target);
+  virtual void GenerateExpectedTargetsCode(std::ostream& os,
+                                           const std::string& expectedTargets);
+  virtual void GenerateImportPropertyCode(std::ostream& os,
+                                          const std::string& config,
+                                          cmGeneratorTarget const* target,
+                                          ImportPropertyMap const& properties);
+  virtual void GenerateMissingTargetsCheckCode(
+    std::ostream& os, const std::vector<std::string>& missingTargets);
+  virtual void GenerateInterfaceProperties(
+    cmGeneratorTarget const* target, std::ostream& os,
+    const ImportPropertyMap& properties);
+  virtual void GenerateImportPrefix(std::ostream& os);
+  virtual void LoadConfigFiles(std::ostream&);
+  virtual void GenerateRequiredCMakeVersion(std::ostream& os,
+                                            const char* versionString);
+  virtual void CleanupTemporaryVariables(std::ostream&);
+  virtual void GenerateImportedFileCheckLoop(std::ostream& os);
+  virtual void GenerateImportedFileChecksCode(
+    std::ostream& os, cmGeneratorTarget* target,
+    ImportPropertyMap const& properties,
+    const std::set<std::string>& importedLocations);
+  virtual bool GenerateImportFileConfig(const std::string& config,
+                                        std::vector<std::string>&);
+};
+
+#endif

+ 97 - 1
Source/cmInstallCommand.cxx

@@ -83,6 +83,8 @@ bool cmInstallCommand::InitialPass(std::vector<std::string> const& args,
     return this->HandleDirectoryMode(args);
   } else if (args[0] == "EXPORT") {
     return this->HandleExportMode(args);
+  } else if (args[0] == "EXPORT_ANDROID_MK") {
+    return this->HandleExportAndroidMKMode(args);
   }
 
   // Unknown mode.
@@ -1097,6 +1099,100 @@ bool cmInstallCommand::HandleDirectoryMode(
   return true;
 }
 
+bool cmInstallCommand::HandleExportAndroidMKMode(
+  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_ANDROID_MK");
+  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(0);
+
+  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(":/\\") != fname.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) != ".mk") {
+    std::ostringstream e;
+    e << args[0] << " given invalid export file name \"" << fname << "\".  "
+      << "The FILE argument must specify a name ending in \".mk\".";
+    this->SetError(e.str());
+    return false;
+  }
+  if (fname.find_first_of(":/\\") != fname.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;
+  }
+  // Use the default name
+  if (fname.empty()) {
+    fname = "Android.mk";
+  }
+
+  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(), true);
+  this->Makefile->AddInstallGenerator(exportGenerator);
+
+  return true;
+#else
+  static_cast<void>(args);
+  this->SetError("EXPORT_ANDROID_MK not supported in bootstrap cmake");
+  return false;
+#endif
+}
+
 bool cmInstallCommand::HandleExportMode(std::vector<std::string> const& args)
 {
   // This is the EXPORT mode.
@@ -1203,7 +1299,7 @@ bool cmInstallCommand::HandleExportMode(std::vector<std::string> const& args)
     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());
+    exportOld.IsEnabled(), false);
   this->Makefile->AddInstallGenerator(exportGenerator);
 
   return true;

+ 1 - 0
Source/cmInstallCommand.h

@@ -48,6 +48,7 @@ private:
   bool HandleFilesMode(std::vector<std::string> const& args);
   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 MakeFilesFullPath(const char* modeName,
                          const std::vector<std::string>& relFiles,
                          std::vector<std::string>& absFiles);

+ 149 - 0
Source/cmInstallExportAndroidMKGenerator.cxx

@@ -0,0 +1,149 @@
+
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+
+  Distributed under the OSI-approved BSD License (the "License");
+  see accompanying file Copyright.txt for details.
+
+  This software is distributed WITHOUT ANY WARRANTY; without even the
+  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the License for more information.
+============================================================================*/
+#include "cmInstallExportAndroidMKGenerator.h"
+
+#include <stdio.h>
+
+#include "cmExportInstallFileGenerator.h"
+#include "cmExportSet.h"
+#include "cmGeneratedFileStream.h"
+#include "cmGlobalGenerator.h"
+#include "cmInstallFilesGenerator.h"
+#include "cmInstallTargetGenerator.h"
+#include "cmLocalGenerator.h"
+#include "cmMakefile.h"
+#include "cmake.h"
+
+cmInstallExportAndroidMKGenerator::cmInstallExportAndroidMKGenerator(
+  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)
+  : cmInstallExportGenerator(exportSet, destination, file_permissions,
+                             configurations, component, message,
+                             exclude_from_all, filename, name_space, exportOld)
+{
+}
+
+cmInstallExportAndroidMKGenerator::~cmInstallExportAndroidMKGenerator()
+{
+}
+
+void cmInstallExportAndroidMKGenerator::Compute(cmLocalGenerator* lg)
+{
+  this->LocalGenerator = lg;
+  this->ExportSet->Compute(lg);
+}
+
+void cmInstallExportAndroidMKGenerator::GenerateScript(std::ostream& os)
+{
+  // Skip empty sets.
+  if (ExportSet->GetTargetExports()->empty()) {
+    std::ostringstream e;
+    e << "INSTALL(EXPORT) given unknown export \"" << ExportSet->GetName()
+      << "\"";
+    cmSystemTools::Error(e.str().c_str());
+    return;
+  }
+
+  // Create the temporary directory in which to store the files.
+  this->ComputeTempDir();
+  cmSystemTools::MakeDirectory(this->TempDir.c_str());
+
+  // Construct a temporary location for the file.
+  this->MainImportFile = this->TempDir;
+  this->MainImportFile += "/";
+  this->MainImportFile += this->FileName;
+
+  // Generate the import file for this export set.
+  this->EFGen->SetExportFile(this->MainImportFile.c_str());
+  this->EFGen->SetNamespace(this->Namespace);
+  this->EFGen->SetExportOld(this->ExportOld);
+  if (this->ConfigurationTypes->empty()) {
+    if (!this->ConfigurationName.empty()) {
+      this->EFGen->AddConfiguration(this->ConfigurationName);
+    } else {
+      this->EFGen->AddConfiguration("");
+    }
+  } else {
+    for (std::vector<std::string>::const_iterator ci =
+           this->ConfigurationTypes->begin();
+         ci != this->ConfigurationTypes->end(); ++ci) {
+      this->EFGen->AddConfiguration(*ci);
+    }
+  }
+  this->EFGen->GenerateImportFile();
+
+  // Perform the main install script generation.
+  this->cmInstallGenerator::GenerateScript(os);
+}
+
+void cmInstallExportAndroidMKGenerator::GenerateScriptConfigs(
+  std::ostream& os, Indent const& indent)
+{
+  // Create the main install rules first.
+  this->cmInstallGenerator::GenerateScriptConfigs(os, indent);
+
+  // Now create a configuration-specific install rule for the import
+  // file of each configuration.
+  std::vector<std::string> files;
+  for (std::map<std::string, std::string>::const_iterator i =
+         this->EFGen->GetConfigImportFiles().begin();
+       i != this->EFGen->GetConfigImportFiles().end(); ++i) {
+    files.push_back(i->second);
+    std::string config_test = this->CreateConfigTest(i->first);
+    os << indent << "if(" << config_test << ")\n";
+    this->AddInstallRule(os, this->Destination, cmInstallType_FILES, files,
+                         false, this->FilePermissions.c_str(), CM_NULLPTR,
+                         CM_NULLPTR, CM_NULLPTR, indent.Next());
+    os << indent << "endif()\n";
+    files.clear();
+  }
+}
+
+void cmInstallExportAndroidMKGenerator::GenerateScriptActions(
+  std::ostream& os, Indent const& indent)
+{
+  // Remove old per-configuration export files if the main changes.
+  std::string installedDir = "$ENV{DESTDIR}";
+  installedDir += this->ConvertToAbsoluteDestination(this->Destination);
+  installedDir += "/";
+  std::string installedFile = installedDir;
+  installedFile += this->FileName;
+  os << indent << "if(EXISTS \"" << installedFile << "\")\n";
+  Indent indentN = indent.Next();
+  Indent indentNN = indentN.Next();
+  Indent indentNNN = indentNN.Next();
+  /* clang-format off */
+  os << indentN << "file(DIFFERENT EXPORT_FILE_CHANGED FILES\n"
+     << indentN << "     \"" << installedFile << "\"\n"
+     << indentN << "     \"" << this->MainImportFile << "\")\n";
+  os << indentN << "if(EXPORT_FILE_CHANGED)\n";
+  os << indentNN << "file(GLOB OLD_CONFIG_FILES \"" << installedDir
+     << this->EFGen->GetConfigImportFileGlob() << "\")\n";
+  os << indentNN << "if(OLD_CONFIG_FILES)\n";
+  os << indentNNN << "message(STATUS \"Old export file \\\"" << installedFile
+     << "\\\" will be replaced.  Removing files [${OLD_CONFIG_FILES}].\")\n";
+  os << indentNNN << "file(REMOVE ${OLD_CONFIG_FILES})\n";
+  os << indentNN << "endif()\n";
+  os << indentN << "endif()\n";
+  os << indent << "endif()\n";
+  /* clang-format on */
+
+  // Install the main export file.
+  std::vector<std::string> files;
+  files.push_back(this->MainImportFile);
+  this->AddInstallRule(os, this->Destination, cmInstallType_FILES, files,
+                       false, this->FilePermissions.c_str(), CM_NULLPTR,
+                       CM_NULLPTR, CM_NULLPTR, indent);
+}

+ 46 - 0
Source/cmInstallExportAndroidMKGenerator.h

@@ -0,0 +1,46 @@
+/*============================================================================
+  CMake - Cross Platform Makefile Generator
+  Copyright 2000-2009 Kitware, Inc., Insight Software Consortium
+
+  Distributed under the OSI-approved BSD License (the "License");
+  see accompanying file Copyright.txt for details.
+
+  This software is distributed WITHOUT ANY WARRANTY; without even the
+  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the License for more information.
+============================================================================*/
+#ifndef cmInstallExportAndroidMKGenerator_h
+#define cmInstallExportAndroidMKGenerator_h
+
+#include "cmInstallExportGenerator.h"
+
+class cmExportInstallFileGenerator;
+class cmInstallFilesGenerator;
+class cmInstallTargetGenerator;
+class cmExportSet;
+class cmMakefile;
+
+/** \class cmInstallExportAndroidMKGenerator
+ * \brief Generate rules for creating an export files.
+ */
+class cmInstallExportAndroidMKGenerator : public cmInstallExportGenerator
+{
+public:
+  cmInstallExportAndroidMKGenerator(
+    cmExportSet* exportSet, const char* dest, const char* file_permissions,
+    const std::vector<std::string>& configurations, const char* component,
+    MessageLevel message, bool exclude_from_all, const char* filename,
+    const char* name_space, bool exportOld);
+  ~cmInstallExportAndroidMKGenerator();
+
+  void Compute(cmLocalGenerator* lg);
+
+protected:
+  virtual void GenerateScript(std::ostream& os);
+  virtual void GenerateScriptConfigs(std::ostream& os, Indent const& indent);
+  virtual void GenerateScriptActions(std::ostream& os, Indent const& indent);
+  void GenerateImportFile(cmExportSet const* exportSet);
+  void GenerateImportFile(const char* config, cmExportSet const* exportSet);
+};
+
+#endif

+ 11 - 2
Source/cmInstallExportGenerator.cxx

@@ -16,6 +16,9 @@
 #include <sstream>
 #include <utility>
 
+#ifdef CMAKE_BUILD_WITH_CMAKE
+#include "cmExportInstallAndroidMKGenerator.h"
+#endif
 #include "cmExportInstallFileGenerator.h"
 #include "cmExportSet.h"
 #include "cmInstallType.h"
@@ -27,7 +30,7 @@ cmInstallExportGenerator::cmInstallExportGenerator(
   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)
+  const char* filename, const char* name_space, bool exportOld, bool android)
   : cmInstallGenerator(destination, configurations, component, message,
                        exclude_from_all)
   , ExportSet(exportSet)
@@ -37,7 +40,13 @@ cmInstallExportGenerator::cmInstallExportGenerator(
   , ExportOld(exportOld)
   , LocalGenerator(CM_NULLPTR)
 {
-  this->EFGen = new cmExportInstallFileGenerator(this);
+  if (android) {
+#ifdef CMAKE_BUILD_WITH_CMAKE
+    this->EFGen = new cmExportInstallAndroidMKGenerator(this);
+#endif
+  } else {
+    this->EFGen = new cmExportInstallFileGenerator(this);
+  }
   exportSet->AddInstallation(this);
 }
 

+ 2 - 1
Source/cmInstallExportGenerator.h

@@ -37,7 +37,8 @@ public:
                            const std::vector<std::string>& configurations,
                            const char* component, MessageLevel message,
                            bool exclude_from_all, const char* filename,
-                           const char* name_space, bool exportOld);
+                           const char* name_space, bool exportOld,
+                           bool android);
   ~cmInstallExportGenerator() CM_OVERRIDE;
 
   cmExportSet* GetExportSet() { return this->ExportSet; }

+ 30 - 0
Tests/RunCMake/AndroidMK/AndroidMK-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}" ANDROID_MK)
+  # clean up new lines
+  string(REGEX REPLACE "\r\n" "\n" ANDROID_MK "${ANDROID_MK}")
+  string(REGEX REPLACE "\n+$" "" ANDROID_MK "${ANDROID_MK}")
+  # 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 "${ANDROID_MK}" MATCHES "${expected}")
+    set(RunCMake_TEST_FAILED
+      "${file} does not match ${expected_file}:
+
+Android.mk contents = [\n${ANDROID_MK}\n]
+Expected = [\n${expected}\n]")
+  endif()
+endmacro()
+
+compare_file_to_expected(
+"${RunCMake_BINARY_DIR}/AndroidMK-build/Android.mk"
+"${RunCMake_TEST_SOURCE_DIR}/expectedBuildAndroidMK.txt")
+compare_file_to_expected(
+"${RunCMake_BINARY_DIR}/AndroidMK-build/CMakeFiles/Export/share/ndk-modules/Android.mk"
+"${RunCMake_TEST_SOURCE_DIR}/expectedInstallAndroidMK.txt")

+ 11 - 0
Tests/RunCMake/AndroidMK/AndroidMK.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  foo.cxx)
+add_library(dog  foo.cxx)
+target_link_libraries(foo car bar dog debug -lm)
+export(TARGETS bar dog car foo  ANDROID_MK
+  ${build_BINARY_DIR}/Android.mk)
+install(TARGETS bar dog car foo DESTINATION lib EXPORT myexp)
+install(EXPORT_ANDROID_MK myexp DESTINATION share/ndk-modules)

+ 3 - 0
Tests/RunCMake/AndroidMK/CMakeLists.txt

@@ -0,0 +1,3 @@
+cmake_minimum_required(VERSION 3.5)
+project(${RunCMake_TEST} NONE) # or languages needed
+include(${RunCMake_TEST}.cmake)

+ 2 - 0
Tests/RunCMake/AndroidMK/RunCMakeTest.cmake

@@ -0,0 +1,2 @@
+include(RunCMake)
+run_cmake(AndroidMK)

+ 23 - 0
Tests/RunCMake/AndroidMK/expectedBuildAndroidMK.txt

@@ -0,0 +1,23 @@
+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.*
+include.*PREBUILT_STATIC_LIBRARY.*
+.*
+include.*CLEAR_VARS.*
+LOCAL_MODULE.*car
+LOCAL_SRC_FILES.*.*car.*
+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
+include.*PREBUILT_STATIC_LIBRARY.*

+ 25 - 0
Tests/RunCMake/AndroidMK/expectedInstallAndroidMK.txt

@@ -0,0 +1,25 @@
+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.*
+include.*PREBUILT_STATIC_LIBRARY.*
+
+include.*CLEAR_VARS.*
+LOCAL_MODULE.*car
+LOCAL_SRC_FILES.*_IMPORT_PREFIX./lib.*car.*
+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
+include.*PREBUILT_STATIC_LIBRARY.*

+ 3 - 0
Tests/RunCMake/AndroidMK/foo.cxx

@@ -0,0 +1,3 @@
+void foo()
+{
+}

+ 2 - 0
Tests/RunCMake/CMakeLists.txt

@@ -331,6 +331,8 @@ add_RunCMake_test_group(CPack "DEB;RPM;TGZ")
 # for MSVC compilers CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS property is used
 add_RunCMake_test(AutoExportDll)
 
+add_RunCMake_test(AndroidMK)
+
 if(CMake_TEST_ANDROID_NDK OR CMake_TEST_ANDROID_STANDALONE_TOOLCHAIN)
   if(NOT "${CMAKE_GENERATOR}" MATCHES "Make|Ninja")
     message(FATAL_ERROR "Android tests supported only by Makefile and Ninja generators")