]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/arrow/dev/archery/archery/utils/cmake.py
import quincy 17.2.0
[ceph.git] / ceph / src / arrow / dev / archery / archery / utils / cmake.py
diff --git a/ceph/src/arrow/dev/archery/archery/utils/cmake.py b/ceph/src/arrow/dev/archery/archery/utils/cmake.py
new file mode 100644 (file)
index 0000000..f93895b
--- /dev/null
@@ -0,0 +1,215 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you 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.
+
+import os
+import re
+from shutil import rmtree, which
+
+from .command import Command, default_bin
+
+
+class CMake(Command):
+    def __init__(self, cmake_bin=None):
+        self.bin = default_bin(cmake_bin, "cmake")
+
+    @staticmethod
+    def default_generator():
+        """ Infer default generator.
+
+        Gives precedence to ninja if there exists an executable named `ninja`
+        in the search path.
+        """
+        found_ninja = which("ninja")
+        return "Ninja" if found_ninja else "Unix Makefiles"
+
+
+cmake = CMake()
+
+
+class CMakeDefinition:
+    """ CMakeDefinition captures the cmake invocation arguments.
+
+    It allows creating build directories with the same definition, e.g.
+    ```
+    build_1 = cmake_def.build("/tmp/build-1")
+    build_2 = cmake_def.build("/tmp/build-2")
+
+    ...
+
+    build1.all()
+    build2.all()
+    """
+
+    def __init__(self, source, build_type="release", generator=None,
+                 definitions=None, env=None):
+        """ Initialize a CMakeDefinition
+
+        Parameters
+        ----------
+        source : str
+                 Source directory where the top-level CMakeLists.txt is
+                 located. This is usually the root of the project.
+        generator : str, optional
+        definitions: list(str), optional
+        env : dict(str,str), optional
+              Environment to use when invoking cmake. This can be required to
+              work around cmake deficiencies, e.g. CC and CXX.
+        """
+        self.source = os.path.abspath(source)
+        self.build_type = build_type
+        self.generator = generator if generator else cmake.default_generator()
+        self.definitions = definitions if definitions else []
+        self.env = env
+
+    @property
+    def arguments(self):
+        """" Return the arguments to cmake invocation. """
+        arguments = [
+            "-G{}".format(self.generator),
+        ] + self.definitions + [
+            self.source
+        ]
+        return arguments
+
+    def build(self, build_dir, force=False, cmd_kwargs=None, **kwargs):
+        """ Invoke cmake into a build directory.
+
+        Parameters
+        ----------
+        build_dir : str
+                    Directory in which the CMake build will be instantiated.
+        force : bool
+                If the build folder exists, delete it before. Otherwise if it's
+                present, an error will be returned.
+        """
+        if os.path.exists(build_dir):
+            # Extra safety to ensure we're deleting a build folder.
+            if not CMakeBuild.is_build_dir(build_dir):
+                raise FileExistsError(
+                    "{} is not a cmake build".format(build_dir)
+                )
+            if not force:
+                raise FileExistsError(
+                    "{} exists use force=True".format(build_dir)
+                )
+            rmtree(build_dir)
+
+        os.mkdir(build_dir)
+
+        cmd_kwargs = cmd_kwargs if cmd_kwargs else {}
+        cmake(*self.arguments, cwd=build_dir, env=self.env, **cmd_kwargs)
+        return CMakeBuild(build_dir, self.build_type, definition=self,
+                          **kwargs)
+
+    def __repr__(self):
+        return "CMakeDefinition[source={}]".format(self.source)
+
+
+CMAKE_BUILD_TYPE_RE = re.compile("CMAKE_BUILD_TYPE:STRING=([a-zA-Z]+)")
+
+
+class CMakeBuild(CMake):
+    """ CMakeBuild represents a build directory initialized by cmake.
+
+    The build instance can be used to build/test/install. It alleviates the
+    user to know which generator is used.
+    """
+
+    def __init__(self, build_dir, build_type, definition=None):
+        """ Initialize a CMakeBuild.
+
+        The caller must ensure that cmake was invoked in the build directory.
+
+        Parameters
+        ----------
+        definition : CMakeDefinition
+                     The definition to build from.
+        build_dir : str
+                    The build directory to setup into.
+        """
+        assert CMakeBuild.is_build_dir(build_dir)
+        super().__init__()
+        self.build_dir = os.path.abspath(build_dir)
+        self.build_type = build_type
+        self.definition = definition
+
+    @property
+    def binaries_dir(self):
+        return os.path.join(self.build_dir, self.build_type)
+
+    def run(self, *argv, verbose=False, **kwargs):
+        cmake_args = ["--build", self.build_dir, "--"]
+        extra = []
+        if verbose:
+            extra.append("-v" if self.bin.endswith("ninja") else "VERBOSE=1")
+        # Commands must be ran under the build directory
+        return super().run(*cmake_args, *extra,
+                           *argv, **kwargs, cwd=self.build_dir)
+
+    def all(self):
+        return self.run("all")
+
+    def clean(self):
+        return self.run("clean")
+
+    def install(self):
+        return self.run("install")
+
+    def test(self):
+        return self.run("test")
+
+    @staticmethod
+    def is_build_dir(path):
+        """ Indicate if a path is CMake build directory.
+
+        This method only checks for the existence of paths and does not do any
+        validation whatsoever.
+        """
+        cmake_cache = os.path.join(path, "CMakeCache.txt")
+        cmake_files = os.path.join(path, "CMakeFiles")
+        return os.path.exists(cmake_cache) and os.path.exists(cmake_files)
+
+    @staticmethod
+    def from_path(path):
+        """ Instantiate a CMakeBuild from a path.
+
+        This is used to recover from an existing physical directory (created
+        with or without CMakeBuild).
+
+        Note that this method is not idempotent as the original definition will
+        be lost. Only build_type is recovered.
+        """
+        if not CMakeBuild.is_build_dir(path):
+            raise ValueError("Not a valid CMakeBuild path: {}".format(path))
+
+        build_type = None
+        # Infer build_type by looking at CMakeCache.txt and looking for a magic
+        # definition
+        cmake_cache_path = os.path.join(path, "CMakeCache.txt")
+        with open(cmake_cache_path, "r") as cmake_cache:
+            candidates = CMAKE_BUILD_TYPE_RE.findall(cmake_cache.read())
+            build_type = candidates[0].lower() if candidates else "release"
+
+        return CMakeBuild(path, build_type)
+
+    def __repr__(self):
+        return ("CMakeBuild["
+                "build = {},"
+                "build_type = {},"
+                "definition = {}]".format(self.build_dir,
+                                          self.build_type,
+                                          self.definition))