Repository: conan-io/conan
Pull request: [poc] CMake + Android
Changelog: Feature: Add POC on a toolchain for Android (using CMake provided modules).
Docs: https://github.com/conan-io/docs/pull/1902


Close #7809 

There are two main different approaches to compile to Android using CMake, both of them should be equivalent:
 * Using CMake provided toolchains: https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-android-with-the-ndk
 * Using AndroidNDK provided toolchain: https://developer.android.com/ndk/guides/cmake#command-line_1

Not sure which one is better, here it is implemented the first one. Do we want to let the user choose?

---

- [ ] There are many TODOs to work on yet

Problem statement:
[feature] CMakeToolchain + Android POC
Implement a proof of concept of using the new CMakeToolchain feature for building:

- Android projects (from Linux/OSX)
- From Windows

Notes:
- Android NDK can be assumed installed
- Focused on creating/building 1 package, both local flow (conan install + native cmake) and ``conan create`` flow.
- Integration test.
- Test can be skipped for CI, but should work locally, annotate assumptions and installation details
- No env-var configuration at all
- All code must be private and local to the toolchains package
- New cross-build model with contexts must be used if necessary, not the old os_build, arch_build
- If enough time to implement reuse of a package, the ``cmake_find_package_multi`` generator must be used

Hints from issue discussion:

Commit message: moving around vars

Patch to review:
diff --git a/conans/client/build/cmake_toolchain_build_helper.py b/conans/client/build/cmake_toolchain_build_helper.py
index b1a19c98702..2df4208dff3 100644
--- a/conans/client/build/cmake_toolchain_build_helper.py
+++ b/conans/client/build/cmake_toolchain_build_helper.py
@@ -4,7 +4,7 @@
 from conans.client import tools
 from conans.client.build import defs_to_string, join_arguments
 from conans.client.build.cmake_flags import is_multi_configuration, get_generator
-from conans.client.toolchain.cmake import CMakeToolchain
+from conans.client.toolchain.cmake.base import CMakeToolchainBase
 from conans.client.tools.files import chdir
 from conans.client.tools.oss import cpu_count, args_to_string
 from conans.errors import ConanException
@@ -74,7 +74,7 @@ def configure(self, source_folder=None):
         if self._build_folder:
             build_folder = os.path.join(self._conanfile.build_folder, self._build_folder)
 
-        defs = {"CMAKE_TOOLCHAIN_FILE": CMakeToolchain.filename}
+        defs = {"CMAKE_TOOLCHAIN_FILE": CMakeToolchainBase.filename}
 
         mkdir(build_folder)
         arg_list = join_arguments([
diff --git a/conans/client/toolchain/cmake/__init__.py b/conans/client/toolchain/cmake/__init__.py
new file mode 100644
index 00000000000..da7d77e91a2
--- /dev/null
+++ b/conans/client/toolchain/cmake/__init__.py
@@ -0,0 +1,16 @@
+from conans.client.tools import cross_building
+from .native import CMakeNativeToolchain
+from .android import CMakeAndroidToolchain
+
+
+def CMakeToolchain(conanfile, **kwargs):
+    if not cross_building(conanfile):
+        return CMakeNativeToolchain(conanfile=conanfile, **kwargs)
+    else:
+        # Exceptions to cross-building scenarios
+        if conanfile.settings.os == 'Windows' and conanfile.settings_build.os == 'Windows':
+            return CMakeNativeToolchain(conanfile=conanfile, **kwargs)
+
+        # Actual cross-building
+        if conanfile.settings.os == 'Android':
+            return CMakeAndroidToolchain(conanfile=conanfile, **kwargs)
diff --git a/conans/client/toolchain/cmake/android.py b/conans/client/toolchain/cmake/android.py
new file mode 100644
index 00000000000..e0741fefcdb
--- /dev/null
+++ b/conans/client/toolchain/cmake/android.py
@@ -0,0 +1,129 @@
+import textwrap
+
+from .base import CMakeToolchainBase
+
+
+class CMakeAndroidToolchain(CMakeToolchainBase):
+    _template_project_include = ''  # TODO: This file is not useful to Android, there is no MSVC runtime MD/MT
+
+    # TODO: Factorize with the native one
+    _template_toolchain = textwrap.dedent("""
+        # Conan automatically generated toolchain file
+        # DO NOT EDIT MANUALLY, it will be overwritten
+
+        # Avoid including toolchain file several times (bad if appending to variables like
+        #   CMAKE_CXX_FLAGS. See https://github.com/android/ndk/issues/323
+        if(CONAN_TOOLCHAIN_INCLUDED)
+          return()
+        endif()
+        set(CONAN_TOOLCHAIN_INCLUDED TRUE)
+
+        # build_type (Release, Debug, etc) is only defined for single-config generators
+        {%- if build_type %}
+        set(CMAKE_BUILD_TYPE "{{ build_type }}" CACHE STRING "Choose the type of build." FORCE)
+        {%- endif %}
+
+        get_property( _CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE )
+        if(_CMAKE_IN_TRY_COMPILE)
+            message(STATUS "Running toolchain IN_TRY_COMPILE")
+            return()
+        endif()
+
+        message("Using Conan toolchain through ${CMAKE_TOOLCHAIN_FILE}.")
+
+        # We are going to adjust automagically many things as requested by Conan
+        #   these are the things done by 'conan_basic_setup()'
+        set(CMAKE_EXPORT_NO_PACKAGE_REGISTRY ON)
+
+        # To support the cmake_find_package generators
+        {% if cmake_module_path -%}
+        set(CMAKE_MODULE_PATH {{ cmake_module_path }} ${CMAKE_MODULE_PATH})
+        {%- endif %}
+        {% if cmake_prefix_path -%}
+        set(CMAKE_PREFIX_PATH {{ cmake_prefix_path }} ${CMAKE_PREFIX_PATH})
+        {%- endif %}
+
+        # shared libs
+        {% if shared_libs -%}
+        message(STATUS "Conan toolchain: Setting BUILD_SHARED_LIBS= {{ shared_libs }}")
+        set(BUILD_SHARED_LIBS {{ shared_libs }})
+        {%- endif %}
+
+        # Parallel builds
+        {% if parallel -%}
+        set(CONAN_CXX_FLAGS "${CONAN_CXX_FLAGS} {{ parallel }}")
+        set(CONAN_C_FLAGS "${CONAN_C_FLAGS} {{ parallel }}")
+        {%- endif %}
+
+        # C++ Standard
+        {% if cppstd -%}
+        message(STATUS "Conan C++ Standard {{ cppstd }} with extensions {{ cppstd_extensions }}}")
+        set(CMAKE_CXX_STANDARD {{ cppstd }})
+        set(CMAKE_CXX_EXTENSIONS {{ cppstd_extensions }})
+        {%- endif %}
+
+        # Install prefix
+        {% if install_prefix -%}
+        set(CMAKE_INSTALL_PREFIX "{{install_prefix}}" CACHE STRING "" FORCE)
+        {%- endif %}
+
+        # Variables
+        {% for it, value in variables.items() -%}
+        set({{ it }} "{{ value }}")
+        {% endfor %}
+        # Variables  per configuration
+        {% for it, values in variables_config.items() -%}
+            {%- set genexpr = namespace(str='') %}
+            {%- for conf, value in values -%}
+                {%- set genexpr.str = genexpr.str +
+                                      '$<IF:$<CONFIG:' + conf + '>,"' + value|string + '",' %}
+                {%- if loop.last %}{% set genexpr.str = genexpr.str + '""' -%}{%- endif -%}
+            {%- endfor -%}
+            {% for i in range(values|count) %}{%- set genexpr.str = genexpr.str + '>' %}
+            {%- endfor -%}
+        set({{ it }} {{ genexpr.str }})
+        {% endfor %}
+
+        # Preprocessor definitions
+        {% for it, value in preprocessor_definitions.items() -%}
+        # add_compile_definitions only works in cmake >= 3.12
+        add_definitions(-D{{ it }}="{{ value }}")
+        {% endfor %}
+        # Preprocessor definitions per configuration
+        {% for it, values in preprocessor_definitions_config.items() -%}
+            {%- set genexpr = namespace(str='') %}
+            {%- for conf, value in values -%}
+                {%- set genexpr.str = genexpr.str +
+                                      '$<IF:$<CONFIG:' + conf + '>,"' + value|string + '",' %}
+                {%- if loop.last %}{% set genexpr.str = genexpr.str + '""' -%}{%- endif -%}
+            {%- endfor -%}
+            {% for i in range(values|count) %}{%- set genexpr.str = genexpr.str + '>' %}
+            {%- endfor -%}
+        add_definitions(-D{{ it }}={{ genexpr.str }})
+        {% endfor %}
+    """)
+
+    # TODO: fPIC, fPIE
+    # TODO: RPATH, cross-compiling to Android?
+    # TODO: libcxx, only libc++ https://developer.android.com/ndk/guides/cpp-support
+
+    def __init__(self, build_type=None, **kwargs):
+        super(CMakeAndroidToolchain, self).__init__(build_type=build_type, **kwargs)
+        # TODO: Is this abuse of 'variables' attribute?
+        self.variables['CMAKE_SYSTEM_NAME'] = 'Android'
+        self.variables['CMAKE_SYSTEM_VERSION'] = self._conanfile.settings.os.api_level
+        self.variables['CMAKE_ANDROID_ARCH_ABI'] = self._get_android_abi()
+        self.variables['CMAKE_ANDROID_NDK'] = '/Users/jgsogo/Library/Android/sdk/ndk/21.0.6113669'  # TODO: ???
+        self.variables['CMAKE_ANDROID_STL_TYPE'] = self._get_android_stl()
+
+        self.build_type = build_type or self._conanfile.settings.get_safe("build_type")
+
+    def _get_android_abi(self):
+        return {"x86": "x86",
+                "x86_64": "x86_64",
+                "armv7": "armeabi-v7a",
+                "armv8": "arm64-v8a"}.get(str(self._conanfile.settings.arch))
+
+    def _get_android_stl(self):
+        libcxx_str = str(self._conanfile.settings.compiler.libcxx)
+        return libcxx_str  # TODO: only 'c++_shared' y 'c++_static' supported?
diff --git a/conans/client/toolchain/cmake/base.py b/conans/client/toolchain/cmake/base.py
new file mode 100644
index 00000000000..3ddafae958b
--- /dev/null
+++ b/conans/client/toolchain/cmake/base.py
@@ -0,0 +1,94 @@
+import os
+from collections import OrderedDict, defaultdict
+
+from jinja2 import Template
+
+from conans.client.build.cmake_flags import is_multi_configuration
+from conans.errors import ConanException
+from conans.util.files import save
+
+
+class Variables(OrderedDict):
+    _configuration_types = None  # Needed for py27 to avoid infinite recursion
+
+    def __init__(self):
+        super(Variables, self).__init__()
+        self._configuration_types = {}
+
+    def __getattribute__(self, config):
+        try:
+            return super(Variables, self).__getattribute__(config)
+        except AttributeError:
+            return self._configuration_types.setdefault(config, dict())
+
+    @property
+    def configuration_types(self):
+        # Reverse index for the configuration_types variables
+        ret = defaultdict(list)
+        for conf, definitions in self._configuration_types.items():
+            for k, v in definitions.items():
+                ret[k].append((conf, v))
+        return ret
+
+
+class CMakeToolchainBase(object):
+    filename = "conan_toolchain.cmake"
+    project_include_filename = "conan_project_include.cmake"
+
+    _template_project_include = None
+    _template_toolchain = None
+
+    def __init__(self, conanfile, **kwargs):
+        self._conanfile = conanfile
+        self.variables = Variables()
+        self.preprocessor_definitions = Variables()
+
+        # To find the generated cmake_find_package finders
+        self.cmake_prefix_path = "${CMAKE_BINARY_DIR}"
+        self.cmake_module_path = "${CMAKE_BINARY_DIR}"
+
+        try:
+            # This is only defined in the cache, not in the local flow
+            self.install_prefix = self._conanfile.package_folder.replace("\\", "/")
+        except AttributeError:
+            # FIXME: In the local flow, we don't know the package_folder
+            self.install_prefix = None
+
+        try:
+            self._build_shared_libs = "ON" if self._conanfile.options.shared else "OFF"
+        except ConanException:
+            self._build_shared_libs = None
+
+        self.build_type = None
+
+    def _get_template_context_data(self):
+        """ Returns two dictionaries, the context for the '_template_toolchain' and
+            the context for the '_template_project_include' templates.
+        """
+        tpl_toolchain_context = {
+            "variables": self.variables,
+            "variables_config": self.variables.configuration_types,
+            "preprocessor_definitions": self.preprocessor_definitions,
+            "preprocessor_definitions_config": self.preprocessor_definitions.configuration_types,
+            "cmake_prefix_path": self.cmake_prefix_path,
+            "cmake_module_path": self.cmake_module_path,
+            "install_prefix": self.install_prefix,
+            "shared_libs": self._build_shared_libs,
+            "build_type": self.build_type,
+        }
+        return tpl_toolchain_context, {}
+
+    def write_toolchain_files(self):
+        tpl_toolchain_context, tpl_project_include_context = self._get_template_context_data()
+
+        # Make it absolute, wrt to current folder, set by the caller
+        conan_project_include_cmake = os.path.abspath(self.project_include_filename)
+        conan_project_include_cmake = conan_project_include_cmake.replace("\\", "/")
+        t = Template(self._template_project_include)
+        content = t.render(**tpl_project_include_context)
+        save(conan_project_include_cmake, content)
+
+        t = Template(self._template_toolchain)
+        content = t.render(conan_project_include_cmake=conan_project_include_cmake,
+                           **tpl_toolchain_context)
+        save(self.filename, content)
diff --git a/conans/client/toolchain/cmake.py b/conans/client/toolchain/cmake/native.py
similarity index 80%
rename from conans/client/toolchain/cmake.py
rename to conans/client/toolchain/cmake/native.py
index 0e65e39b215..53c98898f6b 100644
--- a/conans/client/toolchain/cmake.py
+++ b/conans/client/toolchain/cmake/native.py
@@ -1,15 +1,11 @@
-import os
 import textwrap
-from collections import OrderedDict, defaultdict
 
-from jinja2 import Template
-
-from conans.client.build.cmake_flags import get_generator, get_generator_platform,  get_toolset, \
+from conans.client.build.cmake_flags import get_generator, get_generator_platform, get_toolset, \
     is_multi_configuration
 from conans.client.build.compiler_flags import architecture_flag
 from conans.client.tools import cpu_count
 from conans.errors import ConanException
-from conans.util.files import save
+from .base import CMakeToolchainBase
 
 
 # https://stackoverflow.com/questions/30503631/cmake-in-which-order-are-files-parsed-cache-toolchain-etc
@@ -17,32 +13,7 @@
 # https://github.com/microsoft/vcpkg/tree/master/scripts/buildsystems
 
 
-class Variables(OrderedDict):
-    _configuration_types = None  # Needed for py27 to avoid infinite recursion
-
-    def __init__(self):
-        super(Variables, self).__init__()
-        self._configuration_types = {}
-
-    def __getattribute__(self, config):
-        try:
-            return super(Variables, self).__getattribute__(config)
-        except AttributeError:
-            return self._configuration_types.setdefault(config, dict())
-
-    @property
-    def configuration_types(self):
-        # Reverse index for the configuration_types variables
-        ret = defaultdict(list)
-        for conf, definitions in self._configuration_types.items():
-            for k, v in definitions.items():
-                ret[k].append((conf, v))
-        return ret
-
-
-class CMakeToolchain(object):
-    filename = "conan_toolchain.cmake"
-
+class CMakeNativeToolchain(CMakeToolchainBase):
     _template_toolchain = textwrap.dedent("""
         # Conan automatically generated toolchain file
         # DO NOT EDIT MANUALLY, it will be overwritten
@@ -239,29 +210,18 @@ class CMakeToolchain(object):
 
     def __init__(self, conanfile, generator=None, generator_platform=None, build_type=None,
                  toolset=None, parallel=True):
-        self._conanfile = conanfile
+        super(CMakeNativeToolchain, self).__init__(conanfile)
 
         self.fpic = self._deduce_fpic()
         self.vs_static_runtime = self._deduce_vs_static_runtime()
         self.parallel = parallel
 
-        # To find the generated cmake_find_package finders
-        self.cmake_prefix_path = "${CMAKE_BINARY_DIR}"
-        self.cmake_module_path = "${CMAKE_BINARY_DIR}"
-
         self.generator = generator or get_generator(self._conanfile)
         self.generator_platform = (generator_platform or
                                    get_generator_platform(self._conanfile.settings,
                                                           self.generator))
         self.toolset = toolset or get_toolset(self._conanfile.settings, self.generator)
 
-        self.variables = Variables()
-        self.preprocessor_definitions = Variables()
-        try:
-            self._build_shared_libs = "ON" if self._conanfile.options.shared else "OFF"
-        except ConanException:
-            self._build_shared_libs = None
-
         self.set_libcxx, self.glibcxx = self._get_libcxx()
 
         self.parallel = None
@@ -277,12 +237,6 @@ def __init__(self, conanfile, generator=None, generator_platform=None, build_typ
         # TODO: I would want to have here the path to the compiler too
         build_type = build_type or self._conanfile.settings.get_safe("build_type")
         self.build_type = build_type if not is_multi_configuration(self.generator) else None
-        try:
-            # This is only defined in the cache, not in the local flow
-            self.install_prefix = self._conanfile.package_folder.replace("\\", "/")
-        except AttributeError:
-            # FIXME: In the local flow, we don't know the package_folder
-            self.install_prefix = None
 
     def _deduce_fpic(self):
         fpic = self._conanfile.options.get_safe("fPIC")
@@ -306,7 +260,7 @@ def _get_architecture(self):
     def _deduce_vs_static_runtime(self):
         settings = self._conanfile.settings
         if (settings.get_safe("compiler") == "Visual Studio" and
-                "MT" in settings.get_safe("compiler.runtime")):
+            "MT" in settings.get_safe("compiler.runtime")):
             return True
         return False
 
@@ -352,39 +306,20 @@ def _cppstd(self):
                 cppstd_extensions = "OFF"
         return cppstd, cppstd_extensions
 
-    def write_toolchain_files(self):
-        # Make it absolute, wrt to current folder, set by the caller
-        conan_project_include_cmake = os.path.abspath("conan_project_include.cmake")
-        conan_project_include_cmake = conan_project_include_cmake.replace("\\", "/")
-        t = Template(self._template_project_include)
-        content = t.render(vs_static_runtime=self.vs_static_runtime)
-        save(conan_project_include_cmake, content)
-
-        # TODO: I need the profile_host and profile_build here!
-        # TODO: What if the compiler is a build require?
-        # TODO: Add all the stuff related to settings (ALL settings or just _MY_ settings?)
-
-        context = {
-            "variables": self.variables,
-            "variables_config": self.variables.configuration_types,
-            "preprocessor_definitions": self.preprocessor_definitions,
-            "preprocessor_definitions_config": self.preprocessor_definitions.configuration_types,
-            "build_type": self.build_type,
+    def _get_template_context_data(self):
+        tpl_toolchain_context, tpl_project_include_context = \
+            super(CMakeNativeToolchain, self)._get_template_context_data()
+        tpl_toolchain_context.update({
             "generator_platform": self.generator_platform,
             "toolset": self.toolset,
-            "cmake_prefix_path": self.cmake_prefix_path,
-            "cmake_module_path": self.cmake_module_path,
             "fpic": self.fpic,
             "skip_rpath": self.skip_rpath,
             "set_libcxx": self.set_libcxx,
             "glibcxx": self.glibcxx,
-            "install_prefix": self.install_prefix,
             "parallel": self.parallel,
             "cppstd": self.cppstd,
             "cppstd_extensions": self.cppstd_extensions,
-            "shared_libs": self._build_shared_libs,
             "architecture": self.architecture
-        }
-        t = Template(self._template_toolchain)
-        content = t.render(conan_project_include_cmake=conan_project_include_cmake, **context)
-        save(self.filename, content)
+        })
+        tpl_project_include_context.update({'vs_static_runtime': self.vs_static_runtime})
+        return tpl_toolchain_context, tpl_project_include_context
diff --git a/conans/test/integration/toolchains/__init__.py b/conans/test/integration/toolchains/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/conans/test/integration/toolchains/android/__init__.py b/conans/test/integration/toolchains/android/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/conans/test/integration/toolchains/android/_utils.py b/conans/test/integration/toolchains/android/_utils.py
new file mode 100644
index 00000000000..216c46123fe
--- /dev/null
+++ b/conans/test/integration/toolchains/android/_utils.py
@@ -0,0 +1,38 @@
+import textwrap
+
+lib_h = textwrap.dedent("""
+    int some_function(int value);
+""")
+
+lib_cpp = textwrap.dedent("""
+    #include "lib.h"
+    #include <iostream>
+
+    int some_function(int value) {
+        std::cout << "some_function(value=" << value << ")" << std::endl;
+        return 42;
+    }
+""")
+
+cmakelists = textwrap.dedent("""
+    cmake_minimum_required(VERSION 2.8.12)  # TODO: Define minimun required here
+    project(AndroidLibrary CXX)
+
+    add_library(library lib.h lib.cpp)
+    set_target_properties(library PROPERTIES PUBLIC_HEADER lib.h)
+
+    install(TARGETS library
+        RUNTIME DESTINATION bin
+        LIBRARY DESTINATION lib
+        ARCHIVE DESTINATION lib
+        PUBLIC_HEADER DESTINATION include
+    )
+""")
+
+
+def create_library(client):
+    client.save({
+        'lib.h': lib_h,
+        'lib.cpp': lib_cpp,
+        'CMakeLists.txt': cmakelists
+    })
diff --git a/conans/test/integration/toolchains/android/test_using_cmake.py b/conans/test/integration/toolchains/android/test_using_cmake.py
new file mode 100644
index 00000000000..768a7bc3c05
--- /dev/null
+++ b/conans/test/integration/toolchains/android/test_using_cmake.py
@@ -0,0 +1,81 @@
+import shutil
+import textwrap
+import unittest
+
+from conans.test.utils.tools import TestClient
+from ._utils import create_library
+from conans.client.toolchain.cmake.base import CMakeToolchainBase
+
+class SystemToolsTestCase(unittest.TestCase):
+    # This test assumes that 'CMake' and 'AndroidNDK' are available in the system
+    #
+    # Guidelines: https://developer.android.com/ndk/guides/cmake#command-line
+
+    @classmethod
+    def setUpClass(cls):
+        if not shutil.which('cmake'):
+            raise unittest.SkipTest("CMake expected in PATH")
+        if not shutil.which('cmake'):
+            raise unittest.SkipTest("CMake expected in PATH")
+
+    def setUp(self):
+        current_folder = '/private/var/folders/fc/6mvcrc952dqcjfhl4c7c11ph0000gn/T/tmp4xr45tt5conans/path with spaces'
+        self.t = TestClient(current_folder=current_folder)
+        create_library(self.t)
+        self.t.save({
+            'conanfile.py': textwrap.dedent("""
+                from conans import ConanFile, CMake, CMakeToolchain
+
+                class Library(ConanFile):
+                    name = 'library'
+                    settings = 'os', 'arch', 'compiler', 'build_type'
+                    exports_sources = "CMakeLists.txt", "lib.h", "lib.cpp"
+                    options = {'shared': [True, False]}
+                    default_options = {'shared': False}
+
+                    def toolchain(self):
+                        tc = CMakeToolchain(self)
+                        tc.write_toolchain_files()
+
+                    def build(self):
+                        cmake = CMake(self)
+                        cmake.configure()
+
+                    def package(self):
+                        cmake = CMake(self)
+                        cmake.install()
+                """),
+            'profile_host': textwrap.dedent("""
+                [settings]
+                os=Android
+                os.api_level=23
+                arch=x86_64
+                compiler=clang
+                compiler.version=9
+                compiler.libcxx=c++_shared
+                build_type=Release
+            """)
+        })
+
+    def test_regular_build(self):
+        # TODO: Remove this test, useless besides validating this project
+        self.t.run('create . library/version@ --profile:host=default --profile:build=default')
+
+    def test_use_cmake_toolchain(self):
+        """ This is the naïve approach, we follow instruction from CMake in its documentation
+            https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-android
+        """
+        # Build in the cache
+        self.t.run('create . library/version@ --profile:host=profile_host --profile:build=default')
+
+        # Build locally
+        self.t.run('install . library/version@ --profile:host=profile_host --profile:build=default')
+        self.t.run_command('cmake . -DCMAKE_TOOLCHAIN_FILE={}'.format(CMakeToolchainBase.filename))
+        self.t.run_command('cmake --build .')
+
+    def test_use_android_ndk_toolchain(self):
+        """ Use the CMake toolchain provided by Android NDK itself
+            https://developer.android.com/ndk/guides/cmake#command-line
+        """
+        pass
+
diff --git a/conans/test/integration/toolchains/android/test_using_envvars.py b/conans/test/integration/toolchains/android/test_using_envvars.py
new file mode 100644
index 00000000000..e69de29bb2d


Code review comments:
diff_hunk: @@ -0,0 +1,16 @@
+from conans.client.tools import cross_building
+from .native import CMakeNativeToolchain
+from .android import CMakeAndroidToolchain
+
+
+def CMakeToolchain(conanfile, **kwargs):
+    if not cross_building(conanfile):
+        return CMakeNativeToolchain(conanfile=conanfile, **kwargs)
+    else:
+        # Exceptions to cross-building scenarios
+        if conanfile.settings.os == 'Windows' and conanfile.settings_build.os == 'Windows':
path: conans/client/toolchain/cmake/__init__.py
text: @user1:
why exceptions for Windows? anyway, this sounds like out of scope for Android, don't understand why it should be changed here

@author:
More about it here (https://github.com/conan-io/conan/pull/7841), this PR is built on top of that one. But, specifically, comments related to the Windows exception here (reviews and comments: https://github.com/conan-io/conan/pull/7827), current Conan behavior doesn't consider a cross-building scenario when going from x64 to x86 in Windows or Linux.

diff_hunk: @@ -0,0 +1,81 @@
+import shutil
+import textwrap
+import unittest
+
+from conans.test.utils.tools import TestClient
+from ._utils import create_library
+from conans.client.toolchain.cmake.base import CMakeToolchainBase
+
+class SystemToolsTestCase(unittest.TestCase):
+    # This test assumes that 'CMake' and 'AndroidNDK' are available in the system
+    #
+    # Guidelines: https://developer.android.com/ndk/guides/cmake#command-line
+
+    @classmethod
+    def setUpClass(cls):
+        if not shutil.which('cmake'):
+            raise unittest.SkipTest("CMake expected in PATH")
+        if not shutil.which('cmake'):
path: conans/test/integration/toolchains/android/test_using_cmake.py
text: @user1:
checking the same twice?

@author:
This is an ongoing effort. I'll remove that. My plan was to have something like `which('ndk-build')`, but it looks like it is not in the path by default.