;;; android-common.el --- Common functions/variables to dev Android in Emacs.
;;
;; Copyright (C) 2009 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.

;;; Commentary:
;;
;; Variables to customize and common functions for the Android build
;; support in Emacs.
;; There should be no interactive function in this module.
;;
;; You need to have a proper buildspec.mk file in your root directory
;; for this module to work (see $TOP/build/buildspec.mk.default).
;; If the path the product's files/image uses an a product alias, you
;; need to add a mapping in `android-product-alias-map'. For instance
;; if TARGET_PRODUCT is foo but the build directory is out/target/product/bar,
;; you need to add a mapping Target:foo -> Alias:bar
;;

;;; Code:

(defgroup android nil
  "Support for android development in Emacs."
  :prefix "android-"                    ; Currently unused.
  :tag    "Android"
  :group  'tools)

;;;###autoload
(defcustom android-compilation-jobs 2
  "Number of jobs used to do a compilation (-j option of make)."
  :type 'integer
  :group 'android)

;;;###autoload
(defcustom android-compilation-no-buildenv-warning t
  "If not nil, suppress warnings from the build env (Makefile,
bash) from the compilation output since they interfere with
`next-error'."
  :type 'boolean
  :group 'android)

;;;###autoload
(defcustom android-product-alias-map nil
  "Alist between product targets (declared in buildspec.mk) and actual
 product build directory used by `android-product'.

For instance if TARGET_PRODUCT is 'foo' but the build directory
 is 'out/target/product/bar', you need to add a mapping Target:foo -> Alias:bar."
  :type '(repeat (list (string :tag "Target")
                       (string :tag "Alias")))
  :group 'android)

(defconst android-output-buffer-name "*Android Output*"
  "Name of the default buffer for the output of the commands.
There is only one instance of such a buffer.")

(defun android-find-build-tree-root ()
  "Ascend the current path until the root of the android build tree is found.
Similarly to the shell functions in envsetup.sh, for the root both ./Makefile
and ./build/core/envsetup.mk are exiting files.
Return the root of the build tree.  Signal an error if not found."
  (let ((default-directory default-directory))
    (while (and (> (length default-directory) 2)
                (not (file-exists-p (concat default-directory
                                            "Makefile")))
                (not (file-exists-p (concat default-directory
                                            "build/core/envsetup.mk"))))
      (setq default-directory
            (substring default-directory 0
                       (string-match "[^/]+/$" default-directory))))
    (if (> (length default-directory) 2)
        default-directory
      (error "Not in a valid android tree"))))

(defun android-project-p ()
  "Return nil if not in an android build tree."
  (condition-case nil
      (android-find-build-tree-root)
    (error nil)))

(defun android-host ()
  "Return the <system>-<arch> string (e.g linux-x86).
Only linux and darwin on x86 architectures are supported."
  (or (string-match "x86" system-configuration)
      (string-match "i386" system-configuration)
      (error "Unknown arch"))
  (or (and (string-match "darwin" system-configuration) "darwin-x86")
      (and (string-match "linux" system-configuration) "linux-x86")
      (error "Unknown system")))

(defun android-product ()
  "Return the product built according to the buildspec.mk.
You must have buildspec.mk file in the top directory.

Additional product aliases can be listed in `android-product-alias-map'
if the product actually built is different from the one listed
in buildspec.mk"
  (save-excursion
    (let* ((buildspec (concat (android-find-build-tree-root) "buildspec.mk"))
           (product (with-current-buffer (find-file-noselect buildspec)
                      (goto-char (point-min))
                      (search-forward "TARGET_PRODUCT:=")
                      (buffer-substring-no-properties (point)
                                                      (scan-sexps (point) 1))))
           (alias (assoc product android-product-alias-map)))
      ; Post processing, adjust the names.
      (if (not alias)
          product
        (nth 1 alias)))))

(defun android-product-path ()
  "Return the full path to the product directory.

Additional product aliases can be added in `android-product-alias-map'
if the product actually built is different from the one listed
in buildspec.mk"
  (let ((path (concat (android-find-build-tree-root) "out/target/product/"
                      (android-product))))
    (when (not (file-exists-p path))
      (error (format "%s does not exist. If product %s maps to another one,
add an entry to android-product-map." path (android-product))))
    path))

(defun android-find-host-bin (binary)
  "Return the full path to the host BINARY.
Binaries don't depend on the device, just on the host type.
Try first to locate BINARY in the out/host tree.  Fallback using
the shell exec PATH setup."
  (if (android-project-p)
      (let ((path (concat (android-find-build-tree-root) "out/host/"
                          (android-host) "/bin/" binary)))
        (if (file-exists-p path)
            path
          (error (concat binary " is missing."))))
    (executable-find binary)))

(defun android-adb ()
  "Return the path to the adb executable.
If not in the build tree use the PATH env variable."
  (android-find-host-bin "adb"))

(defun android-fastboot ()
  "Return the path to the fastboot executable.
If not in the build tree use the PATH env variable."
  ; For fastboot -p is the name of the product, *not* the full path to
  ; its directory like adb requests sometimes.
  (concat (android-find-host-bin "fastboot") " -p " (android-product)))

(defun android-adb-command (command &optional product)
  "Execute 'adb COMMAND'.
If the optional PRODUCT is not nil, -p (android-product-path) is used
when adb is invoked."
  (when (get-buffer android-output-buffer-name)
    (with-current-buffer android-output-buffer-name
      (erase-buffer)))
  (if product
      (shell-command (concat (android-adb) " -p " (android-product-path)
                             " " command)
                     android-output-buffer-name)
    (shell-command (concat (android-adb) " " command)
                   android-output-buffer-name)))

(defun android-adb-shell-command (command)
  "Execute 'adb shell COMMAND'."
  (android-adb-command (concat " shell " command)
                       android-output-buffer-name))

(provide 'android-common)

;;; android-common.el ends here