Merge "Build xz using cmake"
diff --git a/ndk/autoconf.py b/ndk/autoconf.py
index 95384c1..22f324f 100644
--- a/ndk/autoconf.py
+++ b/ndk/autoconf.py
@@ -130,7 +130,7 @@
         if env:
             subproc_env.update(env)
         if self.additional_env:
-            subproc_env.update(env)
+            subproc_env.update(self.additional_env)
 
         if subproc_env != os.environ:
             pp_env = pprint.pformat(env, indent=4)
diff --git a/ndk/checkbuild.py b/ndk/checkbuild.py
index cb2567c..6266cfd 100755
--- a/ndk/checkbuild.py
+++ b/ndk/checkbuild.py
@@ -68,6 +68,7 @@
 import ndk.ansi
 import ndk.autoconf
 import ndk.builds
+import ndk.cmake
 import ndk.config
 import ndk.deps
 import ndk.ext.shutil
@@ -1223,12 +1224,10 @@
     def lzma_builder(self) -> ndk.autoconf.AutoconfBuilder:
         """Returns the lazily initialized lzma builder for this module."""
         if self._lzma_builder is None:
-            self._lzma_builder = ndk.autoconf.AutoconfBuilder(
-                self.lzma_src / 'configure',
+            self._lzma_builder = ndk.cmake.CMakeBuilder(
+                self.lzma_src,
                 self.intermediate_out_dir / 'lzma',
-                self.host,
-                add_toolchain_to_path=True,
-                use_clang=True)
+                self.host)
         return self._lzma_builder
 
     @property
@@ -1273,15 +1272,9 @@
 
     def build_lzma(self) -> None:
         """Builds the liblzma dependency."""
-        self.lzma_builder.build([
-            '--disable-shared',
-            '--enable-static',
-            '--disable-xz',
-            '--disable-xzdec',
-            '--disable-lzmadev',
-            '--disable-scripts',
-            '--disable-doc',
-        ])
+        self.lzma_builder.build({
+            'BUILD_SHARED_LIBS': 'OFF',
+        })
 
     def build_gdb(self) -> None:
         """Builds GDB itself."""
diff --git a/ndk/cmake.py b/ndk/cmake.py
new file mode 100644
index 0000000..f7eb116
--- /dev/null
+++ b/ndk/cmake.py
@@ -0,0 +1,193 @@
+#
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""APIs for dealing with cmake scripts."""
+
+import os
+from pathlib import Path
+import pprint
+import shlex
+import shutil
+import subprocess
+from typing import Dict, List, Optional
+
+from ndk.hosts import Host, get_default_host
+import ndk.paths
+import ndk.toolchains
+
+SYSTEM_NAME_MAP = {
+    Host.Darwin: 'Darwin',
+    Host.Linux: 'Linux',
+    Host.Windows64: 'Windows'
+}
+
+HOST_TRIPLE_MAP = {
+    Host.Darwin: 'x86_64-apple-darwin',
+    Host.Linux: 'x86_64-linux-gnu',
+    Host.Windows64: 'x86_64-w64-mingw32',
+}
+
+
+class CMakeBuilder:
+    """Builder for an cmake project."""
+
+    toolchain: ndk.toolchains.Toolchain
+
+    def __init__(self,
+                 src_path: Path,
+                 build_dir: Path,
+                 host: Host,
+                 additional_flags: List[str] = None,
+                 additional_env: Optional[Dict[str, str]] = None) -> None:
+        """Initializes an autoconf builder.
+
+        Args:
+            src_path: Path to the cmake project.
+            build_dir: Directory to use for building. If the directory exists,
+            it will be deleted and recreated to ensure the build is correct.
+            host: Host to be used for the --host argument (the
+                cross-compilation target).
+            additional_flags: Additional flags to pass to the compiler.
+            additional_env: Additional environment to set, used during
+                configure, build, and install.
+        """
+        self.src_path = src_path
+        self.build_directory = build_dir
+        self.host = host
+        self.additional_flags = additional_flags
+        self.additional_env = additional_env
+
+        self.working_directory = self.build_directory / 'build'
+        self.install_directory = self.build_directory / 'install'
+
+        self.toolchain = ndk.toolchains.ClangToolchain(self.host)
+
+    @property
+    def flags(self) -> List[str]:
+        """Returns default cflags for the target."""
+        # TODO: Are these the flags we want? These are what we've used
+        # historically.
+        flags = [
+            '-Os',
+            '-fomit-frame-pointer',
+            '-s',
+        ]
+        if self.additional_flags:
+            flags.extend(self.additional_flags)
+        return flags
+
+    def _run(self, cmd: List[str]) -> None:
+        """Runs and logs execution of a subprocess."""
+        subproc_env = dict(os.environ)
+        if self.additional_env:
+            subproc_env.update(self.additional_env)
+
+        pp_cmd = shlex.join(cmd)
+        if subproc_env != os.environ:
+            pp_env = pprint.pformat(self.additional_env, indent=4)
+            print('Running: {} with env:\n{}'.format(pp_cmd, pp_env))
+        else:
+            print('Running: {}'.format(pp_cmd))
+
+        subprocess.check_call(cmd, env=subproc_env, cwd=self.working_directory)
+
+    @property
+    def _cmake(self) -> Path:
+        return (ndk.paths.ANDROID_DIR / 'prebuilts' / 'cmake' /
+                (get_default_host().value + '-x86') / 'bin' / 'cmake')
+
+    @property
+    def _ninja(self) -> Path:
+        return (ndk.paths.ANDROID_DIR / 'prebuilts' / 'ninja' /
+                (get_default_host().value + '-x86') / 'ninja')
+
+    @property
+    def cmake_defines(self) -> Dict[str, str]:
+        """CMake defines."""
+        flags = self.toolchain.flags + self.flags
+        cflags = ' '.join(flags)
+        cxxflags = ' '.join(flags + ['-stdlib=libc++'])
+        defines: Dict[str, str] = {
+            'CMAKE_C_COMPILER': str(self.toolchain.cc),
+            'CMAKE_C_COMPILER_TARGET': HOST_TRIPLE_MAP[self.host],
+            'CMAKE_CXX_COMPILER': str(self.toolchain.cxx),
+            'CMAKE_CXX_COMPILER_TARGET': HOST_TRIPLE_MAP[self.host],
+            'CMAKE_AR': str(self.toolchain.ar),
+            'CMAKE_RANLIB': str(self.toolchain.ranlib),
+            'CMAKE_NM': str(self.toolchain.nm),
+            'CMAKE_STRIP': str(self.toolchain.strip),
+            'CMAKE_LINKER': str(self.toolchain.ld),
+            'CMAKE_ASM_FLAGS': cflags,
+            'CMAKE_C_FLAGS': cflags,
+            'CMAKE_CXX_FLAGS': cxxflags,
+            'CMAKE_BUILD_TYPE': 'Release',
+            'CMAKE_INSTALL_PREFIX': str(self.install_directory),
+            'CMAKE_MAKE_PROGRAM': str(self._ninja),
+            'CMAKE_SYSTEM_NAME': SYSTEM_NAME_MAP[self.host],
+            'CMAKE_SYSTEM_PROCESSOR': 'x86_64',
+            'CMAKE_FIND_ROOT_PATH_MODE_INCLUDE': 'ONLY',
+            'CMAKE_FIND_ROOT_PATH_MODE_LIBRARY': 'ONLY',
+            'CMAKE_FIND_ROOT_PATH_MODE_PACKAGE': 'ONLY',
+            'CMAKE_FIND_ROOT_PATH_MODE_PROGRAM': 'NEVER',
+        }
+        if self.host.is_windows:
+            defines['CMAKE_RC'] = str(self.toolchain.rescomp)
+        return defines
+
+    def clean(self) -> None:
+        """Cleans output directory.
+
+        If necessary, existing output directory will be removed. After
+        removal, the inner directories (working directory, install directory,
+        and toolchain directory) will be created.
+        """
+        if self.build_directory.exists():
+            shutil.rmtree(self.build_directory)
+
+        self.working_directory.mkdir(parents=True)
+        self.install_directory.mkdir(parents=True)
+
+    def configure(self, additional_defines: Dict[str, str]) -> None:
+        """Invokes cmake configure."""
+        cmake_cmd = [str(self._cmake), '-GNinja']
+        defines = self.cmake_defines
+        defines.update(additional_defines)
+        cmake_cmd.extend(f'-D{key}={val}' for key, val in defines.items())
+        cmake_cmd.append(str(self.src_path))
+
+        self._run(cmake_cmd)
+
+    def make(self) -> None:
+        """Builds the project."""
+        self._run([str(self._ninja)])
+
+    def install(self) -> None:
+        """Installs the project."""
+        self._run([str(self._ninja), 'install'])
+
+    def build(self,
+              additional_defines: Optional[Dict[str, str]] = None) -> None:
+        """Configures and builds an cmake project.
+
+        Args:
+            configure_args: List of arguments to be passed to configure. Does
+                not need to include --prefix, --build, or --host. Those are set
+                up automatically.
+        """
+        self.clean()
+        self.configure(
+            {} if additional_defines is None else additional_defines)
+        self.make()
+        self.install()