;;; projection-multi-cmake.el --- Projection integration for `compile-multi' and the CMake project type. -*- lexical-binding: t; -*-

;; Copyright (C) 2023  Mohsin Kaleem

;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; This library exposes a target generation function for `compile-multi' which
;; sources the list of available targets from a CMake projects build config.
;;
;; This functionality is supported by parsing the set of available targets
;; from the output of the help target (this assumes the project has already
;; passed the configure stage). If invoked prior to this target resolution
;; will return no targets.

;;; Code:

(require 'projection-utils)
(require 'projection-core)
(require 'projection-type-cmake)
(require 'projection-multi)
(require 'projection-types)

(defgroup projection-multi-cmake nil
  "Helpers for `compile-multi' and CMake projects."
  :group 'projection-multi)

(defcustom projection-multi-cmake-exclude-targets
  (rx bol
      (or
       ;; Exclude all the builtin targets generated by CTest when you include(CTest).
       ;; See https://gitlab.kitware.com/cmake/cmake/-/issues/21730.
       "Continuous"
       "ContinuousBuild"
       "ContinuousConfigure"
       "ContinuousCoverage"
       "ContinuousMemCheck"
       "ContinuousStart"
       "ContinuousSubmit"
       "ContinuousTest"
       "ContinuousUpdate"
       "Experimental"
       "ExperimentalBuild"
       "ExperimentalConfigure"
       "ExperimentalCoverage"
       "ExperimentalMemCheck"
       "ExperimentalStart"
       "ExperimentalSubmit"
       "ExperimentalTest"
       "ExperimentalUpdate"
       "Nightly"
       "NightlyBuild"
       "NightlyConfigure"
       "NightlyCoverage"
       "NightlyMemCheck"
       "NightlyMemoryCheck"
       "NightlyStart"
       "NightlySubmit"
       "NightlyTest"
       "NightlyUpdate")
      eol)
  "Regular expression matching CMake targets to exclude from selection.
Configure any targets you never want to be presented with (such as CDash helper
targets) with this option."
  :type '(optional string)
  :group 'projection-multi-cmake)



;;; Code model target backend

(defconst projection-multi-cmake--code-model-meta-targets
  ;; CMake targets [[https://github.com/Kitware/CMake/blob/6e7a1b5b8c42e8d5d0d70aaa9b929ae6d81a79b7/Source/cmGlobalGenerator.cxx#L3179-L3183][defined]] by CMake itself instead of the generator.
  '("all" "install" "clean"))

(defun projection-multi-cmake--targets-from-code-model ()
  "Determine list of available CMake targets from the code-model."
  (when-let* ((targets (projection-cmake--file-api-target-config)))
    (append
     (thread-last
       targets
       (mapcar (apply-partially #'alist-get 'name))
       (projection--uniquify))
     projection-multi-cmake--code-model-meta-targets)))



(defun projection-multi-cmake--dynamic-triggers (project-type)
  "`compile-multi' target generator using dynamic CMake target backend.
Candidates will be prefixed with PROJECT-TYPE."
  (cl-loop
   for target in
   (projection-multi-cmake--targets-from-code-model)
   unless (string-match-p projection-multi-cmake-exclude-targets target)
   collect `(,(concat project-type ":" target)
             :command
             ,(projection-cmake--command 'build target)
             :annotation
             ,(projection-cmake--annotation 'build target))))

(defun projection-multi-cmake--workflow-preset-triggers (project-type)
  "`compile-multi' target generator using CMake workflow presets.
Candidates will be prefixed with PROJECT-TYPE."
  (cl-loop
   for preset in (projection-cmake--list-presets-for-build-type 'workflow)
   do (setq preset (alist-get 'name preset))
   collect `(,(concat project-type ":workflow:" preset)
             :command
             ,(projection-cmake--workflow-command preset)
             :annotation
             ,(projection-cmake--workflow-annotation preset))))

(defconst projection-multi-cmake--unspecified-component-name "Unspecified"
  "See CMAKE_INSTALL_DEFAULT_COMPONENT_NAME.
https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_DEFAULT_COMPONENT_NAME.html")

(defun projection-multi-cmake--install-components (project-type)
  "`compile-multi' target generator for installing individual components.
Candidates will be prefixed with PROJECT-TYPE."
  (cl-loop
   for component in
   (projection-cmake--file-api-install-components)
   collect `(,(if (string-equal component projection-multi-cmake--unspecified-component-name)
                  (concat project-type ":install:no-component")
                (concat project-type ":install:component:" component))
             :command
             ,(projection-cmake--install-command component)
             :annotation
             ,(projection-cmake--install-annotation component))))

;;;###autoload
(defun projection-multi-cmake-targets (&optional project-type)
  "`compile-multi' target generator function for CMake projects.
When set the generated targets will be prefixed with PROJECT-TYPE."
  (setq project-type (or project-type "cmake"))

  (let ((projection-cmake-preset 'silent))
    (append
     (projection-multi-cmake--dynamic-triggers project-type)
     (projection-multi-cmake--workflow-preset-triggers project-type)
     (projection-multi-cmake--install-components project-type))))

;;;###autoload
(defun projection-multi-compile-cmake ()
  "`compile-multi' wrapper for only CMake targets."
  (interactive)
  (projection-multi-compile--run
   (projection--current-project 'no-error)
   `((t ,#'projection-multi-cmake-targets))))

;;;###autoload
(with-eval-after-load 'projection-types
  (projection-type-append-compile-multi-targets projection-project-type-cmake
    #'projection-multi-cmake-targets))

(provide 'projection-multi-cmake)
;;; projection-multi-cmake.el ends here
