From 09e8faac8e3349ae646fcb6a0a7fec405f7d1fbc Mon Sep 17 00:00:00 2001 From: Alexander Yakushev Date: Fri, 21 Aug 2015 03:34:44 +0300 Subject: [PATCH] 0.4.1-snapshot doc update --- index.html | 1345 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 783 insertions(+), 562 deletions(-) diff --git a/index.html b/index.html index 05d8294..fd129a0 100644 --- a/index.html +++ b/index.html @@ -2865,27 +2865,43 @@ build_tree: build_tree }; })(SyntaxHighlighter); -lein-droid/lein-droid -- Marginalia

lein-droid/lein-droid

0.3.1-SNAPSHOT


Plugin for easy Clojure/Android development and deployment

-

dependencies

robert/hooke
1.3.0
org.clojure/data.zip
0.1.1
de.ubercode.clostache/clostache
1.4.0



(this space intentionally left almost blank)
 

Clojure is simple. Android should also be.

+lein-droid/lein-droid -- Marginalia

lein-droid/lein-droid

0.4.1-SNAPSHOT


Plugin for easy Clojure/Android development and deployment

+

dependencies

robert/hooke
1.3.0
org.clojure/data.zip
0.1.1
net.lingala.zip4j/zip4j
1.3.2
com.android.tools.build/manifest-merger
24.2.3
de.ubercode.clostache/clostache
1.4.0



(this space intentionally left almost blank)
 
+
(ns lein-droid.plugin
+  (:require [clojure.java.io :refer [file]]
+            [leiningen.core.main :refer [abort]]
+            [leiningen.droid.utils :refer [ensure-paths]]))

Lein-droid's middleware adds Android SDK local repositories to :repositories. + It has to be done in middleware because artifacts from that repositories are + in :dependencies section, and other Leiningen tasks will crash.

+
(defn middleware
+  [project]
+  (let [sdk-path (file (get-in project [:android :sdk-path]))
+        p (fn [& path] {:url (str "file://" (apply file sdk-path path))})]
+    (ensure-paths sdk-path)
+    (update-in project [:repositories] concat
+               [["android-support" (p "extras" "android" "m2repository")]
+                ["android-play-services" (p "extras" "google" "m2repository")]])))
 

Clojure is simple. Android should also be.

This plugin is intended to make your Clojure/Android development as seamless and efficient as when developing ordinar Clojure JVM programs.

(ns leiningen.droid
   (:refer-clojure :exclude [compile doall repl])
-  (:require clojure.pprint)
+  (:require clojure.pprint
+            [leiningen.droid.aar :refer [extract-aar-dependencies]]
+            [leiningen.droid.code-gen :refer [code-gen]])
   (:use [leiningen.core.project :only [set-profiles]]
         [leiningen.core.main :only [abort]]
         [leiningen.help :only (subtask-help-for)]
         [leiningen.clean :only [clean]]
-        [leiningen.droid.compile :only [compile code-gen]]
+        [leiningen.droid.compile :only [compile]]
         [leiningen.droid
          [classpath :only [init-hooks]]
-         [build :only [create-dex create-obfuscated-dex
+         [build :only [create-dex
                        crunch-resources package-resources create-apk
-                       sign-apk zipalign-apk apk build jar]]
-         [deploy :only [install run forward-port repl deploy]]
+                       sign-apk zipalign-apk apk build jar aar]]
+         [deploy :only [install run forward-port repl deploy local-repo]]
          [new :only [new init]]
-         [compatibility :only [gather-dependencies]]
+         [test :only [local-test]]
          [utils :only [proj wrong-usage android-parameters ensure-paths
                        dev-build?]]]))

Shows the list of possible lein droid subtasks.

(defn help
@@ -2901,36 +2917,28 @@
 
(declare execute-subtask)

Metatask. Performs all Android tasks from compilation to deployment.

(defn doall
   [{{:keys [library]} :android :as project} & device-args]
-  (let [build-steps (if library ["build"] ["build" "apk" "deploy"])
-        build-steps (if (dev-build? project)
-                      build-steps (cons "clean" build-steps))]
+  (let [build-steps (if library ["build"] ["build" "apk" "deploy"])]
     (doseq [task build-steps]
-      (execute-subtask project task device-args))))

DEPRECATED. Metatask. Builds, packs and deploys the release version of the - project.

-
(defn release
-  [project & args]
-  (abort (str "\"release\" subtask is deprecated, "
-              "please use 'lein with-profile release droid doall'")))

Supertask for Android-related tasks (see lein droid for list).

+ (execute-subtask project task device-args))))

Supertask for Android-related tasks (see lein droid for list).

(defn ^{:no-project-needed true
-        :subtasks [#'new #'init #'code-gen #'compile
-                   #'create-dex #'create-obfuscated-dex
+        :subtasks [#'new #'init #'code-gen #'compile #'create-dex
                    #'crunch-resources #'package-resources
                    #'create-apk #'sign-apk #'zipalign-apk
                    #'install #'run #'forward-port #'repl
-                   #'build #'apk #'deploy #'doall #'release #'help
-                   #'gather-dependencies #'jar #'pprint]}
+                   #'build #'apk #'deploy #'doall #'help #'local-test
+                   #'jar #'pprint]}
   droid
   ([project]
      (help #'droid))
   ([project & [cmd & args]]
      (init-hooks)
-     (some-> project
-             android-parameters
-             (execute-subtask cmd args))))

Executes a subtask defined by name on the given project.

+ (when (and (nil? project) (not (#{"new" "help" "init"} cmd))) + (abort "Subtask" cmd "should be run from the project folder.")) + (doto (android-parameters project) + extract-aar-dependencies + (execute-subtask cmd args))))

Executes a subtask defined by name on the given project.

(defn execute-subtask
   [project name args]
-  (when (and (nil? project) (not (#{"new" "help" "init"} name)))
-    (abort "Subtask" name "should be run from the project folder."))
   (case name
     ;; Standalone tasks
     "new" (if (< (count args) 2)
@@ -2940,7 +2948,6 @@
     "code-gen" (code-gen project)
     "compile" (compile project)
     "create-dex" (create-dex project)
-    "create-obfuscated-dex" (create-obfuscated-dex project)
     "crunch-resources" (crunch-resources project)
     "package-resources" (package-resources project)
     "create-apk" (create-apk project)
@@ -2950,20 +2957,74 @@
     "run" (apply run project args)
     "forward-port" (apply forward-port project args)
     "repl" (repl project)
-    "gather-dependencies" (apply gather-dependencies project args)
     "clean" (clean project)
+    "local-repo" (local-repo project)
+    ;; Test tasks
+    "local-test" (apply local-test project args)
     ;; Meta tasks
     "build" (build project)
     "apk" (apk project)
     "deploy" (apply deploy project args)
     "doall" (apply doall project args)
-    "release" (apply release project args)
     "jar" (jar project)
+    "aar" (aar project)
     ;; Help tasks
     "pprint" (apply pprint project args)
     "help" (help #'droid)
     (println "Subtask is not recognized:" name
-             (subtask-help-for nil #'droid))))
 

A set of functions and subtasks responsible for building the + (subtask-help-for nil #'droid))))

 

Utilities for manipulating Android package format (AAR).

+
(ns leiningen.droid.aar
+  (:require [clojure.java.io :as io]
+            [clojure.edn :as edn]
+            [leiningen.core.classpath :as cp]
+            [leiningen.core.main :refer [debug]])
+  (:import java.io.File
+           net.lingala.zip4j.core.ZipFile))

Returns a list of artifact dependencies that have aar extension.

+
(defn- get-aar-dependencies
+  [project]
+  (let [deps (cp/get-dependencies :dependencies project)]
+    (for [[[art-id ver & opts :as dep]] deps
+          :let [opts (apply hash-map opts)]
+          :when (= (:extension opts) "aar")]
+      dep)))

Takes a dependency vector and returns its stringified version to be used in a + file system.

+
(defn- str-dependency
+  [dep]
+  (-> (meta dep)
+      :dependency
+      .getArtifact
+      str
+      (.replace ":" "_")))

Unpacks all AAR dependencies of the project into the target directory.

+
(defn extract-aar-dependencies
+  [{:keys [target-path] :as project}]
+  (let [deps (set (get-aar-dependencies project))
+        aar-extracted-dir (io/file target-path "aar-extracted")
+        ;; Read which AARs we already extracted to avoid doing it again.
+        extracted-file (io/file aar-extracted-dir "extracted.edn")
+        already-extracted (when (.exists ^File extracted-file)
+                            (edn/read-string (slurp extracted-file)))]
+    (when-not (or (empty? deps) (= deps already-extracted))
+      (debug "Extracting AAR dependencies: " deps)
+      (doseq [dep deps]
+        (.extractAll (ZipFile. (:file (meta dep)))
+                     (str (io/file aar-extracted-dir (str-dependency dep)))))
+      (spit extracted-file deps))))

Returns the list of files or directories specified by subpath extracted + from each AAR dependency.

+
(defn get-aar-files
+  [{:keys [target-path] :as project} & subpath]
+  (let [aar-extracted-dir (io/file target-path "aar-extracted")]
+    (for [dep (get-aar-dependencies project)]
+      (apply io/file aar-extracted-dir (str-dependency dep) subpath))))

Returns the list of all jars extracted from all AAR dependencies.

+
(defn get-aar-classes
+  [project]
+  (let [classes-jars (get-aar-files project "classes.jar")
+        libs-dirs (get-aar-files project "libs")]
+    (concat (filter #(.exists ^File %) classes-jars)
+            (mapcat #(.listFiles ^File %) libs-dirs))))

Returns the list of existing paths to native libraries extracted from AAR + dependencies.

+
(defn get-aar-native-paths
+  [project]
+  (filter #(.exists ^File %) (get-aar-files project "jni")))
 

A set of functions and subtasks responsible for building the Android project.

(ns leiningen.droid.build
   (:refer-clojure :exclude [compile])
@@ -2971,150 +3032,183 @@
          [classpath :only [resolve-dependencies]]
          [main :only [debug info abort *debug*]]]
         [leiningen.droid
-         [compile :only [code-gen compile]]
+         [compile :only [compile]]
          [utils :only [get-sdk-android-jar sh dev-build?
                        ensure-paths with-process read-password append-suffix
                        create-debug-keystore get-project-file read-project
-                       sdk-binary relativize-path get-sdk-support-jars
-                       get-resource-jars]]
-         [manifest :only [write-manifest-with-internet-permission]]])
-  (:require [clojure.string]
-            [clojure.set]
+                       sdk-binary relativize-path get-sdk-annotations-jar
+                       get-resource-jars get-sdk-build-tools-path]]])
+  (:require [clojure.string :as str]
+            [clojure.set :as set]
             [clojure.java.io :as io]
-            [leiningen.droid.sdk :as sdk]
-            leiningen.jar leiningen.javac)
-  (:import java.io.File))

Build-related subtasks

-

Run dex on the given target which should be either directory with .class -files or jar file, e.g. one produced by proguard.

+ leiningen.core.project + [leiningen.droid + [code-gen :refer [code-gen]] + [aar :refer [get-aar-files]] + [manifest :refer [get-package-name]] + [sdk :as sdk]] + [leiningen.jar :as jar] + leiningen.javac leiningen.pom) + (:import java.io.File + net.lingala.zip4j.core.ZipFile + net.lingala.zip4j.model.ZipParameters + net.lingala.zip4j.util.Zip4jConstants))

Build-related subtasks

+

Run proguard on the compiled classes and dependencies, create an JAR with + minimized and shaken classes.

+
(defn- run-proguard-minifying
+  [{{:keys [external-classes-paths
+            proguard-conf-path proguard-opts proguard-output-jar-path]} :android
+            compile-path :compile-path :as project}]
+  (info "Running Proguard...")
+  (ensure-paths compile-path proguard-conf-path)
+  (let [proguard-bin (sdk-binary project :proguard)
+        android-jar (get-sdk-android-jar project)
+        annotations (get-sdk-annotations-jar project)
+        deps (resolve-dependencies :dependencies project)
+        external-paths (or external-classes-paths [])
+        proguard-opts (or proguard-opts [])]
+    (sh proguard-bin (str "@" proguard-conf-path)
+        "-injars" (->> (concat [compile-path] deps external-paths)
+                       (map str)
+                       (str/join ":"))
+        "-libraryjars" (->> [annotations android-jar]
+                            (map str)
+                            (str/join ":"))
+        "-outjars" proguard-output-jar-path
+        proguard-opts)))

Run proguard on the compiled classes and dependencies to determine which + classes have to be kept in primary dex.

+
(defn- run-proguard-multidexing
+  [{{:keys [multi-dex-proguard-conf-path multi-dex-root-classes-path]} :android
+    :as project} target-paths]
+  (ensure-paths multi-dex-proguard-conf-path)
+  (let [proguard-bin (sdk-binary project :proguard)
+        android-jar (io/file (get-sdk-build-tools-path project)
+                             "lib" "shrinkedAndroid.jar")]
+    (sh proguard-bin (str "@" multi-dex-proguard-conf-path)
+        "-injars" (str/join ":" target-paths)
+        "-libraryjars" (str android-jar)
+        "-outjars" multi-dex-root-classes-path)))

Creates a text file with the list of classes that should be included into + primary dex.

+
(defn- generate-main-dex-list
+  [{{:keys [multi-dex-root-classes-path multi-dex-main-dex-list-path]} :android
+    :as project}
+   target-paths]
+  (run-proguard-multidexing project target-paths)
+  (let [dx-jar (io/file (get-sdk-build-tools-path project) "lib" "dx.jar")
+        builder (ProcessBuilder.
+                 ["java" "-cp" (str dx-jar) "com.android.multidex.MainDexListBuilder"
+                  multi-dex-root-classes-path (str/join ":" target-paths)])
+        process-name (.start builder)
+        output (line-seq (io/reader (.getInputStream process-name)))
+        writer (io/writer (io/file multi-dex-main-dex-list-path))]
+    (binding [*out* writer]
+      (doseq [line output]
+        (println line)))
+    (.waitFor process-name)))

Run dex on the given target paths, each should be either a directory with + .class files or a jar file.

Since the execution of dx takes a pretty lot of time we need to ensure that its subprocess will be killed if user cancels the build (sends SIGINT to leiningen). That is why we add a hook to the runtime that will be triggered when Leiningen is closed.

(defn- run-dx
-  [{{:keys [sdk-path out-dex-path external-classes-paths
-            force-dex-optimize dex-opts support-libraries]} :android,
-            :as project}
-   target]
+  [{{:keys [out-dex-path force-dex-optimize dex-opts multi-dex
+            multi-dex-root-classes-path multi-dex-main-dex-list-path]} :android :as project}
+   target-paths]
+  (if multi-dex
+    (do (info "Creating multi DEX....")
+        (generate-main-dex-list project target-paths))
+    (info "Creating DEX...."))
   (let [dx-bin (sdk-binary project :dx)
         options (or dex-opts [])
         no-optimize (if (and (not force-dex-optimize) (dev-build? project))
                       "--no-optimize" [])
-        annotations (str sdk-path "/tools/support/annotations.jar")
-        deps (resolve-dependencies :dependencies project)
-        support-jars (get-sdk-support-jars sdk-path support-libraries true)
-        external-classes-paths (or external-classes-paths [])]
-    (with-process [proc (map str
-                             (flatten [dx-bin options "--dex" no-optimize
-                                       "--output" out-dex-path
-                                       target annotations deps
-                                       support-jars
-                                       external-classes-paths]))]
+        annotations (get-sdk-annotations-jar project)
+        multi-dex (if multi-dex
+                    ["--multi-dex" "--main-dex-list" multi-dex-main-dex-list-path]
+                    [])]
+    (with-process [proc (->> [dx-bin options "--dex" no-optimize multi-dex
+                              "--output" out-dex-path
+                              target-paths annotations]
+                             flatten
+                             (map str))]
       (.addShutdownHook (Runtime/getRuntime) (Thread. #(.destroy proc))))))

Creates a DEX file from the compiled .class files.

(defn create-dex
-  [{compile-path :compile-path :as project}]
-  (info "Creating DEX....")
-  (ensure-paths compile-path)
-  (run-dx project compile-path))

Creates an obfuscated DEX file from the compiled .class files.

-
(defn create-obfuscated-dex
-  [{{:keys [sdk-path out-dex-path external-classes-paths
-            force-dex-optimize dex-opts target-version
-            proguard-conf-path proguard-opts]} :android,
-            compile-path :compile-path
-            project-name :name
-            target-path :target-path
-            :as project}]
-  (info "Creating obfuscated DEX....")
-  (ensure-paths compile-path proguard-conf-path)
-  (when-not (.isDirectory (io/file compile-path))
-    (abort (format "compile-path (%s) is not a directory" compile-path)))
-  (let [obfuscated-jar-file (str (io/file target-path
-                                          (str project-name "-obfuscated.jar")))
-        proguard-jar (sdk-binary project :proguard)
-        android-jar (get-sdk-android-jar sdk-path target-version)
-        proguard-opts (or proguard-opts [])
-        annotations (str sdk-path "/tools/support/annotations.jar")
-        deps (resolve-dependencies :dependencies project)
-        external-paths (or external-classes-paths [])
-        compile-path-dir (io/file compile-path)
-        ;; to figure out what classes were thrown away by proguard
-        orig-class-files
-        (when *debug*
-          (set (for [file (file-seq compile-path-dir)
-                     :when (and (.isFile ^File file)
-                                (.endsWith (str file) ".class"))]
-                 (relativize-path compile-path-dir file))))]
-    (sh "java"
-        "-jar" proguard-jar
-        (str "@" proguard-conf-path)
-        "-injars" compile-path
-        "-outjars" obfuscated-jar-file
-        "-libraryjars" (->> (concat [annotations android-jar]
-                                    deps external-paths)
-                            (map str)
-                            (clojure.string/join ":"))
-        proguard-opts)
-    (when *debug*
-      (let [optimized-class-files
-            (for [file (binding [*debug* false]
-                         ;; Supress this output
-                         (sh "jar" "tf" obfuscated-jar-file))
-                  :let [trimmed (clojure.string/trim-newline file)]
-                  :when (.endsWith ^String trimmed ".class")]
-              trimmed)
-            thrown-away-classes (clojure.set/difference orig-class-files
-                                                        optimized-class-files)]
-        (cond (empty? thrown-away-classes) nil
-              (< (count thrown-away-classes) 30)
-              (doseq [class thrown-away-classes]
-                (debug class))
-              :else
-              (let [file (io/file target-path "removed-classes.txt")]
-                (debug
-                 (format "%s classes were removed by ProGuard. See list in %s."
-                         (count thrown-away-classes) file))
-                (spit file (clojure.string/join "\n" thrown-away-classes))))))
-    (run-dx project obfuscated-jar-file)))

Updates the pre-processed PNG cache.

- -

Calls aapt binary with the crunch task.

-
(defn crunch-resources
-  [{{:keys [res-path out-res-path]} :android :as project}]
-  (info "Crunching resources...")
-  (ensure-paths res-path)
-  (let [aapt-bin (sdk-binary project :aapt)]
-    (sh aapt-bin "crunch -v"
-        "-S" res-path
-        "-C" out-res-path)))

We have to declare a future reference here because build and -build-project-dependencies are mutually-recursive.

-
(declare build)

Builds all project dependencies for the current project.

-
(defn build-project-dependencies
-  [{{:keys [project-dependencies]} :android, root :root}]
-  (doseq [dep-path project-dependencies
-          :let [dep-project (read-project (get-project-file root dep-path))]]
-    (info "Building project dependency" dep-path "...")
-    (build dep-project)
-    (info "Building dependency complete.")))

Metatask. Builds dependencies, compiles and creates DEX (if not a library).

+ [{{:keys [sdk-path external-classes-paths + proguard-execute proguard-output-jar-path]} :android, + compile-path :compile-path :as project}] + (if proguard-execute + (do + (run-proguard-minifying project) + (run-dx project proguard-output-jar-path)) + (let [deps (resolve-dependencies :dependencies project) + external-classes-paths (or external-classes-paths [])] + (run-dx project (concat [compile-path] deps external-classes-paths)))))

Metatask. Compiles the project and creates DEX.

(defn build
   [{{:keys [library]} :android :as project}]
-  (if library
-    (doto project
-      build-project-dependencies code-gen compile crunch-resources)
-    (doto project
-      build-project-dependencies code-gen compile create-dex)))

Metatask. Packages compiled Java files and Clojure sources into JAR.

+ (doto project + code-gen compile create-dex))

Metatask. Packages compiled Java files and Clojure sources into JAR.

Same as lein jar but appends Android libraries to the classpath while compiling Java files.

(defn jar
   [project]
   (leiningen.javac/javac project)
-  (leiningen.jar/jar project))

APK-related subtasks

-

Packages application resources.

- -

If this task is run with :dev profile, then it ensures that - AndroidManifest.xml has Internet permission for running the REPL - server. This is achieved by backing up the original manifest file - and creating a new one with Internet permission appended to it. - After the packaging the original manifest file is restored.

+ (jar/write-jar project (jar/get-jar-filename project) + (#'jar/filespecs (dissoc project :java-source-paths))))

Metatask. Packages library into AAR archive.

+
(defn aar
+  [{{:keys [manifest-path res-path gen-path assets-paths]} :android
+    :keys [name version target-path compile-path root] :as project}]
+  (code-gen project)
+  (.renameTo (io/file gen-path "R.txt") (io/file target-path "R.txt"))
+  (leiningen.javac/javac project)
+  ;; Remove unnecessary files
+  (.delete ^File (io/file gen-path "R.java.d"))
+  (let [package-name (get-package-name manifest-path)
+        classes-path (apply io/file compile-path (str/split package-name #"\."))]
+    (doseq [^File file (.listFiles ^File classes-path)
+            :let [filename (.getName file)]
+            :when (or (= filename "R.class")
+                      (re-matches #"R\$\w+\.class" filename))]
+      (.delete file)))
+  ;; Make a JAR
+  (jar/write-jar project (io/file target-path "classes.jar")
+                 (#'jar/filespecs (dissoc project :java-source-paths)))
+  ;; Finally create AAR file
+  (let [zip (ZipFile. (io/file target-path (format "%s-%s.aar" name version)))
+        params (doto (ZipParameters.)
+                 (.setCompressionMethod Zip4jConstants/COMP_STORE)
+                 ;; (.setDefaultFolderPath "target")
+                 (.setEncryptFiles false))]
+    (.addFile zip (io/file manifest-path) params)
+    (.addFile zip (io/file target-path "classes.jar") params)
+    (.addFile zip (io/file target-path "R.txt") params)
+    (.addFolder zip (io/file res-path) params)
+    (when (.exists (io/file root "libs"))
+      (.addFolder zip (io/file root "libs") params))
+    (doseq [path assets-paths
+            :when (.exists (io/file path))]
+      (.addFolder zip (io/file path) params)))
+  (leiningen.pom/pom (assoc project :packaging "aar")))

APK-related subtasks

+

Updates the pre-processed PNG cache.

+ +

Calls aapt binary with the crunch task.

+ +

Because of the AAPT bug we must turn paths to files here so that proper +canonical names are calculated.

+
(defn crunch-resources
+  [{{:keys [res-path out-res-path]} :android :as project}]
+  (info "Crunching resources...")
+  (ensure-paths res-path)
+  (let [aapt-bin (sdk-binary project :aapt)
+        crunch (fn [src-dir target-dir]
+                 (sh aapt-bin "crunch -v" "-S" src-dir "-C" target-dir))]
+    (doseq [aar (get-aar-files project)
+            :when (.exists ^File (io/file aar "R.txt"))
+            :let [out (io/file aar "out-res")]]
+      (.mkdirs ^File out)
+      (crunch (io/file aar "res") out))
+    (crunch (io/file res-path) (io/file out-res-path))))

Packages application resources.

(defn package-resources
   [{{:keys [sdk-path target-version manifest-path assets-paths res-path
             out-res-path external-res-paths out-res-pkg-path
@@ -3123,44 +3217,40 @@
   (ensure-paths sdk-path manifest-path res-path)
   (let [aapt-bin (sdk-binary project :aapt)
         android-jar (get-sdk-android-jar sdk-path target-version)
-        dev-build (dev-build? project)
-        debug-mode (if dev-build ["--debug-mode"] [])
-        manifest-file (io/file manifest-path)
-        backup-file (io/file (str manifest-path ".backup"))
-        ;; Only add `assets` directory if it is present.
-        assets (mapcat #(when (.exists (io/file %)) ["-A" %])
-                       (conj assets-paths assets-gen-path))
+        debug-mode (if (dev-build? project) ["--debug-mode"] [])
+        ;; Only add `assets` directories when they are present.
+        assets (mapcat #(when (.exists (io/file %)) ["-A" (str %)])
+                       (concat assets-paths [assets-gen-path]
+                               (get-aar-files project "assets")))
+        aar-resources (for [res (get-aar-files project "res")] ["-S" res])
+        aar-crunched-resources (for [res (get-aar-files project "out-res")
+                                     :when (.exists ^File res)]
+                                 ["-S" res])
         external-resources (for [res external-res-paths] ["-S" res])]
-    (when dev-build
-      (io/copy manifest-file backup-file)
-      (write-manifest-with-internet-permission manifest-path))
     (sh aapt-bin "package" "--no-crunch" "-f" debug-mode "--auto-add-overlay"
         "-M" manifest-path
         "-S" out-res-path
         "-S" res-path
+        aar-crunched-resources
+        aar-resources
         external-resources
         assets
         "-I" android-jar
         "-F" out-res-pkg-path
         "--generate-dependencies"
         (if rename-manifest-package
-          ["--rename-manifest-package" rename-manifest-package] []))
-    (when dev-build
-      (io/copy backup-file manifest-file)
-      (io/delete-file backup-file))))

Creates a deployment-ready APK file.

+ ["--rename-manifest-package" rename-manifest-package] []))))

Creates a deployment-ready APK file.

It is done by executing methods from ApkBuilder SDK class on the generated DEX-file and the resource package.

(defn create-apk
-  [{{:keys [out-apk-path out-res-pkg-path
-            out-dex-path resource-jars-paths]} :android,
-            java-only :java-only :as project}]
+  [{{:keys [out-apk-path out-res-pkg-path resource-jars-paths]} :android
+    :as project}]
   (info "Creating APK...")
-  (ensure-paths out-res-pkg-path out-dex-path)
-  (let [suffix (if (dev-build? project) "debug-unaligned" "unaligned")
-        unaligned-path (append-suffix out-apk-path suffix)
+  (ensure-paths out-res-pkg-path)
+  (let [unaligned-path (append-suffix out-apk-path "unaligned")
         resource-jars (concat (get-resource-jars project)
-                              (map #(java.io.File. %) resource-jars-paths))]
+                              (map io/file resource-jars-paths))]
     (sdk/create-apk project
                     :apk-name unaligned-path :resource-jars resource-jars)))

Signs APK file with the key taken from the keystore.

@@ -3168,25 +3258,24 @@ whether the build type is the debug one. Creates a debug keystore if it is missing.

(defn sign-apk
-  [{{:keys [out-apk-path sigalg
+  [{{:keys [out-apk-path sigalg use-debug-keystore
             keystore-path key-alias keypass storepass]} :android :as project}]
   (info "Signing APK with" keystore-path "...")
-  (let [dev-build (dev-build? project)
-        suffix (if dev-build "debug-unaligned" "unaligned")
-        unaligned-path (append-suffix out-apk-path suffix)
+  (let [debug (or (dev-build? project) use-debug-keystore)
+        unaligned-path (append-suffix out-apk-path "unaligned")
         sigalg (or sigalg "SHA1withRSA")]
-    (when (and dev-build (not (.exists (io/file keystore-path))))
+    (when (and debug (not (.exists (io/file keystore-path))))
       ;; Create a debug keystore if there isn't one
       (create-debug-keystore keystore-path))
     (ensure-paths unaligned-path keystore-path)
-    (let [storepass     (or (when dev-build "android")
-                            storepass
-                            (System/getenv "STOREPASS")
-                            (read-password "Enter storepass: "))
-          keypass       (or (when dev-build "android")
-                            keypass
-                            (System/getenv "KEYPASS")
-                            (read-password "Enter keypass: "))]
+    (let [storepass (or (when debug "android")
+                        storepass
+                        (System/getenv "STOREPASS")
+                        (read-password "Enter storepass: "))
+          keypass   (or (when debug "android")
+                        keypass
+                        (System/getenv "KEYPASS")
+                        (read-password "Enter keypass: "))]
       (sh "jarsigner"
           "-sigalg" sigalg
           "-digestalg" "SHA1"
@@ -3200,14 +3289,10 @@
   [{{:keys [sdk-path out-apk-path]} :android :as project}]
   (info "Aligning APK...")
   (let [zipalign-bin (sdk-binary project :zipalign)
-        unaligned-suffix (if (dev-build? project) "debug-unaligned" "unaligned")
-        unaligned-path (append-suffix out-apk-path unaligned-suffix)
-        aligned-path (if (dev-build? project)
-                       (append-suffix out-apk-path "debug")
-                       out-apk-path)]
+        unaligned-path (append-suffix out-apk-path "unaligned")]
     (ensure-paths unaligned-path)
-    (.delete (io/file aligned-path))
-    (sh zipalign-bin "4" unaligned-path aligned-path)))

Metatask. Crunches and packages resources, creates, signs and aligns an APK.

+ (.delete (io/file out-apk-path)) + (sh zipalign-bin "4" unaligned-path out-apk-path)))

Metatask. Crunches and packages resources, creates, signs and aligns an APK.

(defn apk
   [project]
   (doto project
@@ -3215,10 +3300,10 @@
     create-apk sign-apk zipalign-apk))
 

Contains functions and hooks for Android-specific classpath manipulation.

(ns leiningen.droid.classpath
-  (:use [robert.hooke :only [add-hook]]
-        [leiningen.droid.utils :only [get-sdk-android-jar
-                                      get-sdk-google-api-jars
-                                      get-sdk-support-jars]])
+  (:require [leiningen.droid.aar :refer [get-aar-classes]]
+            [leiningen.droid.utils :refer [get-sdk-android-jar
+                                           get-sdk-annotations-jar]]
+            [robert.hooke :refer [add-hook]])
   (:import org.sonatype.aether.util.version.GenericVersionScheme))

Since dx and ApkBuilder utilities fail when they are feeded repeated jar-files, we need to make sure that JAR dependencies list contains only unique jars.

@@ -3260,7 +3345,14 @@ ;; so we transform a list into a map with nil values. (zipmap (remove-duplicate-dependencies (keys all-deps)) (repeat nil)) - all-deps)))

We also have to manually attach Android SDK libraries to the + all-deps)))

Takes the original resolve-dependencies function and arguments to it. + Appends jar files extracted from AAR dependencies.

+
(defn- resolve-dependencies-hook
+  [f dependency-key project & rest]
+  (let [deps (apply f dependency-key project rest)]
+    (if (= dependency-key :dependencies)
+      (concat deps (get-aar-classes project))
+      deps)))

We also have to manually attach Android SDK libraries to the classpath. The reason for this is that Leiningen doesn't handle external dependencies at the high level, and Android jars are not distributed in a convenient fashion (using Maven repositories). To @@ -3270,79 +3362,98 @@ Then the path to the actual android.jar file is constructed and appended to the rest of the classpath list.

(defn classpath-hook
-  [f {{:keys [sdk-path target-version external-classes-paths
-              use-google-api support-libraries]}
-      :android :as project}]
+  [f {{:keys [external-classes-paths]} :android :as project}]
   (let [classpath (f project)
-        result (conj (concat classpath external-classes-paths
-                             (when use-google-api
-                               (get-sdk-google-api-jars sdk-path
-                                                        target-version))
-                             (get-sdk-support-jars sdk-path support-libraries))
-                     (get-sdk-android-jar sdk-path target-version)
-                     (str sdk-path "/tools/support/annotations.jar"))]
+        result (conj (concat classpath external-classes-paths)
+                     (get-sdk-android-jar project)
+                     (get-sdk-annotations-jar project))]
     result))
(defn init-hooks []
   (add-hook #'leiningen.core.classpath/get-dependencies #'dependencies-hook)
-  (add-hook #'leiningen.core.classpath/get-classpath #'classpath-hook))
 

Contains utilities for letting lein-droid to cooperate with - ant/Eclipse build tools.

-
(ns leiningen.droid.compatibility
-  (:require [clojure.java.io :as io])
-  (:use [leiningen.core
-         [main :only [info]]
-         [classpath :only [resolve-dependencies]]]
-        [leiningen.droid.utils :only [ensure-paths]]))

Compatibility task. Copies the dependency libraries into the libs/ folder.

-
(defn gather-dependencies
-  [{:keys [root] :as project} & {dir ":dir", :or {dir "libs"} :as other}]
-  (println (class (first (keys other))))
-  (info "Copying dependency libraries into" (str dir "..."))
-  (let [destination-dir (io/file root dir)
-        dependencies (resolve-dependencies :dependencies project)]
-    (.mkdirs destination-dir)
-    (doseq [dep dependencies]
-      (io/copy dep
-               (io/file destination-dir (.getName ^java.io.File dep))))))

Creates a file named .nrepl-port in project directory with port - number inside, so that fireplace.vim can connect to the REPL.

-
(defn create-repl-port-file
-  [{{:keys [repl-local-port]} :android, root :root}]
-  (spit (io/file root ".nrepl-port") repl-local-port))
 

This part of the plugin is responsible for the project compilation.

-
(ns leiningen.droid.compile
-  (:refer-clojure :exclude [compile])
-  (:require [leiningen compile javac]
-            [clojure.java.io :as io]
-            [clojure.set :as sets]
-            [leiningen.core.eval :as eval])
-  (:use [leiningen.droid.utils :only [get-sdk-android-jar sdk-binary
-                                      ensure-paths sh dev-build?]]
-        [leiningen.droid.manifest :only [get-package-name generate-manifest]]
-        [leiningen.core
-         [main :only [debug info abort]]
-         [classpath :only [get-classpath]]]
-        [bultitude.core :only [namespaces-on-classpath]]))

Pre-compilation tasks

-

Save project's data-readers value to application's resources so - it can be later retrieved in runtime. This is necessary to be able - to use data readers when developing in REPL on the device.

-
(defn save-data-readers-to-resource
-  [{{:keys [assets-gen-path]} :android :as project}]
-  (.mkdirs (io/file assets-gen-path))
-  (eval/eval-in-project
-   project
-   `(spit (io/file ~assets-gen-path "data_readers.clj")
-          (into {} (map (fn [[k# v#]]
-                          [k# (symbol (subs (str v#) 2))])
-                        *data-readers*)))))

Generates the R.java file from the resources.

- -

This task is necessary if you define the UI in XML and also to gain - access to your strings and images by their ID.

+ (add-hook #'leiningen.core.classpath/resolve-dependencies #'resolve-dependencies-hook) + (add-hook #'leiningen.core.classpath/get-classpath #'classpath-hook))
 

Tasks and related functions for build-specific code generation.

+
(ns leiningen.droid.code-gen
+  (:require [clojure.java.io :as io]
+            [clojure.string :as str]
+            [clostache.parser :as clostache]
+            [leiningen.core.main :refer [debug info abort]]
+            [leiningen.droid.aar :refer [get-aar-files]]
+            [leiningen.droid.manifest :refer [get-package-name generate-manifest]]
+            [leiningen.droid.sideload :as sideload]
+            [leiningen.droid.utils :refer [get-sdk-android-jar sdk-binary
+                                           ensure-paths sh dev-build?]]
+            [leiningen.new.templates :refer [slurp-resource]])
+  (:import java.io.File))

BuildConfig.java generation

+

Mapping of classes to type strings as they should appear in BuildConfig.

+
(defn- java-type
+  [x]
+  (condp = (type x)
+    Boolean "boolean"
+    String  "String"
+    Long    "long"
+    Double  "double"
+    (abort ":build-config only supports boolean, String, long and double types.")))

Transform a map of constants return to form readable by Clostache.

+
(defn map-constants
+  [constants]
+  (map (fn [[k v]]
+         (binding [*print-dup* true]
+           {:key k
+            :value (pr-str v)
+            :type (java-type v)}))
+       constants))
+
(defn generate-build-constants
+  [{{:keys [manifest-path gen-path build-config rename-manifest-package]}
+    :android, version :version :as project}]
+  (ensure-paths manifest-path)
+  (let [res (io/resource "templates/BuildConfig.java")
+        package-name (get-package-name manifest-path)
+        gen-package-path (apply io/file gen-path (str/split package-name #"\."))
+        application-id (or rename-manifest-package package-name)
+        template-constants (-> (merge {"VERSION_NAME"   version
+                                       "APPLICATION_ID" application-id}
+                                      build-config)
+                               map-constants)]
+    (ensure-paths gen-package-path)
+    (->> {:debug        (dev-build? project)
+          :package-name package-name
+          :constants    template-constants}
+         (clostache/render (slurp-resource res))
+         (spit (io/file gen-package-path "BuildConfig.java")))))

R.java generation

+

Generates R.java file given full symbols file, library symbols file and + library package name. Symbols file are loaded from respective R.txt files.

+
(defn create-r-file
+  [full-symbols lib-r-txt lib-package gen-path]
+  (debug "Generating R.java file for:" lib-package)
+  (let [symbols (sideload/symbol-loader lib-r-txt)
+        writer (sideload/symbol-writer (str gen-path) lib-package full-symbols)]
+    (.load symbols)
+    (.addSymbolsToWrite writer symbols)
+    (.write writer)))

Generates R.java files for the project and all dependency libraries, having + R.txt for project and each library.

+
(defn generate-r-files
+  [{{:keys [sdk-path gen-path manifest-path]} :android :as project}]
+  (sideload/sideload-jars sdk-path)
+  (let [full-r-txt (io/file gen-path "R.txt")
+        full-symbols (sideload/symbol-loader full-r-txt)]
+    (.load full-symbols)
+    (dorun
+     (map (fn [manifest, ^File r-txt]
+            (when (.exists r-txt)
+              (let [package-name (get-package-name manifest)
+                    lib-gen-path gen-path]
+                (create-r-file full-symbols r-txt package-name lib-gen-path))))
+          (get-aar-files project "AndroidManifest.xml")
+          (get-aar-files project "R.txt")))))

Generates R.java files for both the project and the libraries.

(defn generate-resource-code
   [{{:keys [sdk-path target-version manifest-path res-path gen-path
             out-res-path external-res-paths library]} :android
-    java-only :java-only :as project}]
-  (info "Generating R.java...")
+            java-only :java-only :as project}]
+  (info "Generating R.java files...")
   (let [aapt-bin (sdk-binary project :aapt)
         android-jar (get-sdk-android-jar sdk-path target-version)
         manifest-file (io/file manifest-path)
-        library-specific (if library "--non-constant-id" "--auto-add-overlay")
+        library-specific (if library ["--non-constant-id"] [])
+        aar-resources (for [res (get-aar-files project "res")] ["-S" (str res)])
         external-resources (for [res external-res-paths] ["-S" res])]
     (ensure-paths manifest-path res-path android-jar)
     (.mkdirs (io/file gen-path))
@@ -3351,36 +3462,82 @@
         "-M" manifest-path
         "-S" out-res-path
         "-S" res-path
+        aar-resources
         external-resources
         "-I" android-jar
         "-J" gen-path
-        "--generate-dependencies"))
-  project)

Generates R.java and builds a manifest with the appropriate version + "--output-text-symbols" gen-path + "--auto-add-overlay" + "--generate-dependencies") + ;; Finally generate R.java files having R.txt keys + (when-not library + (generate-r-files project))))

Generates R.java and builds a manifest with the appropriate version code and substitutions.

(defn code-gen
-  [project]
-  (doto project generate-manifest generate-resource-code))

Compilation

-

Stores a set of namespaces that should always be compiled -regardless of the build type. Since these namespaces are used in -eval-in-project call they naturally don't get AOT-compiled during -automatic dependency resolution, so we have to make sure they are -compiled anyway.

-
(def ^:private always-compile-ns
-  '#{clojure.core clojure.core.protocols clojure.string
-     clojure.java.io})

Takes project and returns a set of namespaces that should be AOT-compiled.

+ [{{:keys [library]} :android :as project}] + (doto project + generate-manifest generate-resource-code + generate-build-constants))
 

This part of the plugin is responsible for the project compilation.

+
(ns leiningen.droid.compile
+  (:refer-clojure :exclude [compile])
+  (:require [bultitude.core :as bultitude]
+            [clojure.java.io :as io]
+            [clojure.set :as set]
+            [leiningen.compile :refer [stale-namespaces]]
+            [leiningen.core.classpath :refer [get-classpath]]
+            [leiningen.core.eval :as eval]
+            [leiningen.core.main :refer [debug info abort]]
+            [leiningen.droid.manifest :refer [get-package-name]]
+            [leiningen.droid.utils :refer [ensure-paths dev-build?]]
+            leiningen.javac)
+  (:import java.util.regex.Pattern))

Pre-compilation tasks

+
+
(defn eval-in-project
+  ([project form init]
+   (eval/prep project)
+   (eval/eval-in project
+                 `(do ~@(map (fn [[k v]] `(set! ~k ~v)) (:global-vars project))
+                      ~init
+                      ~@(:injections project)
+                      ~form)))
+  ([project form] (eval-in-project project form nil)))

Save project's data-readers value to application's resources so + it can be later retrieved in runtime. This is necessary to be able + to use data readers when developing in REPL on the device.

+
(defn save-data-readers-to-resource
+  [{{:keys [assets-gen-path]} :android :as project}]
+  (.mkdirs (io/file assets-gen-path))
+  (eval-in-project
+   project
+   `(spit (io/file ~assets-gen-path "data_readers.clj")
+          (into {} (map (fn [[k# v#]]
+                          [k# (symbol (subs (str v#) 2))])
+                        *data-readers*)))))

Compilation

+

Takes project and returns a set of namespaces that should be AOT-compiled.

(defn namespaces-to-compile
   [{{:keys [aot aot-exclude-ns]} :android :as project}]
-  (-> (case aot
-        :all
-          (seq (leiningen.compile/stale-namespaces (assoc project :aot :all)))
-        :all-with-unused
-          (namespaces-on-classpath :classpath
-                                   (map io/file (get-classpath project)))
-        ;; else
-          (map symbol aot))
-      set
-      (sets/union always-compile-ns)
-      (sets/difference (set (map symbol aot-exclude-ns)))))

Compiles Clojure files into .class files.

+ (let [all-nses (bultitude/namespaces-on-classpath + :classpath (map io/file (get-classpath project))) + include (case aot + :all (stale-namespaces (assoc project :aot :all)) + :all-with-unused all-nses + aot) + exclude aot-exclude-ns + {include-nses false, include-regexps true} + (group-by #(instance? Pattern %) include) + {exclude-nses false, exclude-regexps true} + (group-by #(instance? Pattern %) exclude)] + (->> (set/difference (set (map str (if (seq include-regexps) + all-nses include-nses))) + (set exclude-nses)) + (filter (fn [ns] (if (seq include-regexps) + (some #(re-matches % ns) include-regexps) + true))) + (remove (fn [ns] (if (seq exclude-regexps) + (some #(re-matches % ns) exclude-regexps) + false))) + (concat (if (seq include-regexps) + include-nses ())) + (map symbol))))

Compiles Clojure files into .class files.

If :aot project parameter equals :all then compiles the necessary dependencies. If :aot equals :all-with-unused then @@ -3393,15 +3550,17 @@ debug-specific code when building the release.

(defn compile-clojure
   [{{:keys [enable-dynamic-compilation start-nrepl-server
-            manifest-path repl-device-port ignore-log-priority]}
+            manifest-path repl-device-port ignore-log-priority
+            lean-compile skummet-skip-vars]}
     :android
     {:keys [nrepl-middleware]} :repl-options
     :as project}]
   (info "Compiling Clojure files...")
-  (ensure-paths manifest-path)
   (debug "Project classpath:" (get-classpath project))
   (let [nses (namespaces-to-compile project)
         dev-build (dev-build? project)
+        package-name (try (get-package-name manifest-path)
+                          (catch Exception ex nil))
         opts (cond-> {:neko.init/release-build (not dev-build)
                       :neko.init/start-nrepl-server start-nrepl-server
                       :neko.init/nrepl-port repl-device-port
@@ -3409,45 +3568,62 @@
                       enable-dynamic-compilation
                       :neko.init/ignore-log-priority ignore-log-priority
                       :neko.init/nrepl-middleware (list 'quote nrepl-middleware)
-                      :neko.init/package-name (get-package-name manifest-path)}
-                     (not dev-build) (assoc :elide-meta
-                                       [:doc :file :line :column :added :arglists]))]
+                      :neko.init/package-name package-name}
+               (not dev-build) (assoc :elide-meta
+                                      [:doc :file :line :column :added :author
+                                       :static :arglists :forms]))]
     (info (format "Build type: %s, dynamic compilation: %s, remote REPL: %s."
-                  (if dev-build "debug" "release")
+                  (if dev-build "debug" (if lean-compile "lean" "release"))
                   (if (or dev-build start-nrepl-server
                           enable-dynamic-compilation)
                     "enabled" "disabled")
                   (if (or dev-build start-nrepl-server) "enabled" "disabled")))
     (let [form
-          `(binding [*compiler-options* ~opts]
-             (doseq [namespace# '~nses]
-               (println "Compiling" namespace#)
-               (clojure.core/compile namespace#)))
-          project (update-in project [:prep-tasks]
-                             (partial remove #{"compile"}))]
+          (if lean-compile
+            `(let [lean-var?# (fn [var#] (not (#{~@skummet-skip-vars}
+                                              (str var#))))]
+               (binding [~'clojure.core/*lean-var?* lean-var?#
+                         ~'clojure.core/*lean-compile* true
+                         ~'clojure.core/*compiler-options* ~opts]
+                 (doseq [namespace# '~nses]
+                   (println "Compiling" namespace#)
+                   (clojure.core/compile namespace#))
+                 (shutdown-agents)))
+            `(binding [*compiler-options* ~opts]
+               ;; If expectations is present, don't run it during compilation.
+               (doseq [namespace# '~nses]
+                 (println "Compiling" namespace#)
+                 (clojure.core/compile namespace#))
+               (try (require 'expectations)
+                    ((resolve 'expectations/disable-run-on-shutdown))
+                    (catch Throwable _# nil))
+               (shutdown-agents)))]
       (.mkdirs (io/file (:compile-path project)))
-      (try (eval/eval-in-project project form)
+      (try (eval-in-project project form)
            (info "Compilation succeeded.")
            (catch Exception e
              (abort "Compilation failed."))))))

Compiles both Java and Clojure source files.

(defn compile
-  [{{:keys [sdk-path gen-path]} :android, java-only :java-only :as project} & args]
+  [{{:keys [sdk-path gen-path lean-compile]} :android,
+    java-only :java-only :as project}]
   (ensure-paths sdk-path)
-  (let [project (update-in project [:java-source-paths] conj gen-path)]
-    (when-not java-only
-      (save-data-readers-to-resource project))
-    (apply leiningen.javac/javac project args)
+  (let [project (-> project
+                    (update-in [:prep-tasks] (partial remove #{"compile"})))]
+    (leiningen.javac/javac project)
     (when-not java-only
+      (save-data-readers-to-resource project)
       (compile-clojure project))))
 

Functions and subtasks that install and run the application on the device and manage its runtime.

(ns leiningen.droid.deploy
   (:use [leiningen.core.main :only [debug info abort *debug*]]
         [leiningen.droid.manifest :only (get-launcher-activity
                                          get-package-name)]
-        [leiningen.droid.utils :only [sh ensure-paths dev-build? append-suffix
+        [leiningen.droid.utils :only [sh ensure-paths append-suffix
                                       prompt-user sdk-binary]]
-        [leiningen.droid.compatibility :only (create-repl-port-file)]
-        [reply.main :only (launch-nrepl)]))

Returns the list of currently attached devices.

+ [reply.main :only (launch-nrepl)]) + (:require [clojure.java.io :as io] + [cemerick.pomegranate.aether :as aether] + [reply.initialization :as reply-init]))

Returns the list of currently attached devices.

(defn- device-list
   [adb-bin]
   (let [output (rest (sh adb-bin "devices"))] ;; Ignore the first line
@@ -3496,19 +3672,16 @@
 manually parse its output to figure out what is going on. This is
 why this subtask is full of low-level stuff.

(defn install
-  [{{:keys [out-apk-path manifest-path]} :android :as project}
-   & device-args]
+  [{{:keys [out-apk-path manifest-path rename-manifest-package]}
+    :android :as project} & device-args]
   (info "Installing APK...")
   (let [adb-bin (sdk-binary project :adb)
-        apk-path (if (dev-build? project)
-                   (append-suffix out-apk-path "debug")
-                   out-apk-path)
-        _ (ensure-paths apk-path)
+        _ (ensure-paths out-apk-path)
         device (get-device-args adb-bin device-args)
         output (java.io.StringWriter.)]
     ;; Rebind *out* to get the output `adb` produces.
     (binding [*out* output, *debug* true]
-      (sh adb-bin device "install" "-r" apk-path))
+      (sh adb-bin device "install" "-r" out-apk-path))
     (let [output (str output)
           response (some
                      adb-responses
@@ -3516,11 +3689,13 @@
       (case response
         :success (debug output)
         :inconsistent-certificates
-        (let [resp (prompt-user uninstall-prompt)]
+        (let [resp (prompt-user uninstall-prompt)
+              package-name (or rename-manifest-package
+                               (get-package-name manifest-path))]
           (if (.equalsIgnoreCase "y" resp)
             (do
-              (sh adb-bin device "uninstall" (get-package-name manifest-path))
-              (sh adb-bin device "install" apk-path))
+              (sh adb-bin device "uninstall" package-name)
+              (sh adb-bin device "install" out-apk-path))
             (abort "Cannot proceed with installation.")))
         (do (info output)
             (abort "Abort execution."))))))

Launches the installed APK on the connected device.

@@ -3536,44 +3711,71 @@

This allows to connect to the remote REPL from the current machine.

(defn forward-port
-  [{{:keys [repl-device-port repl-local-port]} :android :as project}
+  [{{:keys [repl-device-port repl-local-port]} :android, root :root :as project}
    & device-args]
   (info "Binding device port" repl-device-port
         "to local port" repl-local-port "...")
-  (create-repl-port-file project)
+  (spit (io/file root ".nrepl-port") repl-local-port)
   (let [adb-bin (sdk-binary project :adb)
         device (get-device-args adb-bin device-args)]
     (sh adb-bin device "forward"
         (str "tcp:" repl-local-port)
-        (str "tcp:" repl-device-port))))

Connects to a remote nREPL server on the device using REPLy.

+ (str "tcp:" repl-device-port))))

Substitution for REPLy's own default-init-function.

+
(defn default-init
+  [{:keys [custom-help] :as options}]
+  `(do
+     ~@reply-init/prelude
+     (use '[clojure.repl :only ~'[source apropos dir doc pst find-doc]])
+     (use '[clojure.pprint :only ~'[pp pprint]])
+     (defn ~'help
+       "Prints a list of helpful commands."
+       []
+       (println "        Exit: Control+D or (exit) or (quit)")
+       (println "    Commands: (user/help)")
+       (println "        Docs: (doc function-name-here)")
+       (println "              (find-doc \"part-of-name-here\")")
+       (println "      Source: (source function-name-here)"))
+     (user/help)
+     nil))

Connects to a remote nREPL server on the device using REPLy.

(defn repl
   [{{:keys [repl-local-port]} :android}]
-  (launch-nrepl {:attach (str "localhost:" repl-local-port)}))

Metatask. Runs install,run,forward-port`.

+ (with-redefs [reply-init/default-init-code default-init] + (launch-nrepl {:attach (str "localhost:" repl-local-port)})))

Metatask. Runs install,run,forward-port`.

(defn deploy
   [project & device-args]
   (let [adb-bin (sdk-binary project :adb)
         device (get-device-args adb-bin device-args)]
     (apply install project device)
     (apply run project device)
-    (apply forward-port project device)))
 

Contains functions to manipulate AndroidManifest.xml file

+ (apply forward-port project device)))

Install the generated AAR package to the local Maven repository.

+
(defn local-repo
+  [{:keys [target-path name group version root] :as project}]
+  (leiningen.pom/pom (assoc project :packaging "aar"))
+  (let [aar-file (io/file target-path (format "%s-%s.aar" name version))]
+    (ensure-paths aar-file)
+    (->> {[:extension "pom"] (io/file root "pom.xml")
+          [:extension "aar"] aar-file}
+         (#'aether/artifacts-for [(symbol group name) version])
+         (aether/install-artifacts :files))))
 

Contains functions to manipulate AndroidManifest.xml file

(ns leiningen.droid.manifest
   (:require [clojure.data.zip.xml :refer :all]
             [clojure.xml :as xml]
             [clojure.java.io :as jio]
-            [clojure.zip :refer [append-child node up xml-zip]]
+            [clojure.string :as str]
+            [clojure.zip :refer [up xml-zip]]
             [clostache.parser :as clostache]
-            [leiningen.core.main :refer [info]]
+            [leiningen.core.main :refer [info debug abort]]
+            [leiningen.droid.aar :refer [get-aar-files]]
+            [leiningen.droid.utils :refer [dev-build?]]
             [leiningen.release :refer [parse-semantic-version]])
-  (:import (java.io FileWriter)))

Constants

+ (:import com.android.manifmerger.ManifestMerger + com.android.manifmerger.MergerLog + [com.android.utils StdLogger StdLogger$Level] + java.io.File))

Constants

Name of the category for the launcher activities.

-
(def ^{:private true} launcher-category "android.intent.category.LAUNCHER")

Name of the Internet permission.

-
(def ^{:private true} internet-permission "android.permission.INTERNET")

XML tag of the Internet permission.

-
(def ^{:private true} internet-permission-tag
-  {:tag :uses-permission
-   :attrs {(keyword :android:name) internet-permission}})

Attribute name for target SDK version.

+
(def ^{:private true} launcher-category "android.intent.category.LAUNCHER")

Attribute name for target SDK version.

(def ^:private target-sdk-attribute (keyword :android:targetSdkVersion))

Attribute name for minimal SDK version.

-
(def ^:private min-sdk-attribute (keyword :android:minSdkVersion))

Attribute name for project version name.

-
(def ^:private version-name-attribute (keyword :android:versionName))

Local functions

+
(def ^:private min-sdk-attribute (keyword :android:minSdkVersion))

Local functions

Parses given XML manifest file and creates a zipper from it.

(defn- load-manifest
   [manifest-path]
@@ -3582,15 +3784,7 @@
 
(defn- get-all-launcher-activities
   [manifest]
   (xml-> manifest :application :activity :intent-filter :category
-         (attr= :android:name launcher-category)))

Checks if manifest contains Internet permission.

-
(defn- has-internet-permission?
-  [manifest]
-  (first (xml-> manifest
-                :uses-permission (attr= :android:name internet-permission))))

Writes the manifest to the specified filename.

-
(defn- write-manifest
-  [manifest filename]
-  (binding [*out* (FileWriter. filename)]
-    (xml/emit (node manifest))))

Public functions

+ (attr= :android:name launcher-category)))

Manifest parsing and data extraction

Returns the name of the application's package.

(defn get-package-name
   [manifest-path]
@@ -3607,35 +3801,29 @@
         pkg-name (first (xml-> manifest (attr :package)))]
     (when activity-name
       (str (or rename-manifest-package pkg-name) "/"
-           (str pkg-name activity-name)))))

Updates the manifest on disk guaranteed to have the Internet permission.

-
(defn write-manifest-with-internet-permission
-  [manifest-path]
-  (let [manifest (load-manifest manifest-path)]
-   (write-manifest (if (has-internet-permission? manifest)
-                     manifest
-                     (append-child manifest internet-permission-tag))
-                   manifest-path)))

Extracts the target SDK version from the provided manifest file. If + (if (.startsWith activity-name ".") + (str pkg-name activity-name) + activity-name)))))

Extracts the target SDK version from the provided manifest file. If target SDK is not specified returns minimal SDK.

(defn get-target-sdk-version
   [manifest-path]
-  (let [[uses-sdk] (xml-> (load-manifest manifest-path) :uses-sdk)
-        [target-sdk] (xml-> uses-sdk (attr target-sdk-attribute))]
-    (or target-sdk
-        (first (xml-> uses-sdk (attr min-sdk-attribute))))))

Extracts the project version name from the provided manifest file.

-
(defn get-project-version
-  [manifest-path]
-  (first (xml-> (load-manifest manifest-path) (attr version-name-attribute))))
-
(def ^:private version-bit-sizes [9 9 9 5])
+ (let [[uses-sdk] (xml-> (load-manifest manifest-path) :uses-sdk)] + (or (first (xml-> uses-sdk (attr target-sdk-attribute))) + (first (xml-> uses-sdk (attr min-sdk-attribute))))))

Manifest templating

+

Maximum values per each version bucket.

(def ^:private version-maximums
-  (mapv (partial bit-shift-left 1) version-bit-sizes))
+ (mapv (partial bit-shift-left 1) [9 9 9 5]))

Each part of the version number will be multiplied by the respective + coefficient, all of which are calculated here.

(def ^:private version-coefficients
-  (mapv (fn [offset] (bit-shift-left 1 (- 32 offset)))
-        (reductions + version-bit-sizes)))

Asserts that a>b in version segments

+ (->> version-maximums + (reductions +) + (mapv (fn [offset] (bit-shift-left 1 (- 32 offset))))))

Asserts that a>b in version segments

(defn- assert>
   [a b]
-  (assert (> a b) (str "Version number segment too large to fit in the
-  version-code scheme " b ">" a ", maximum version in each segment
-  is " (clojure.string/join "." version-maximums)))
+  (when-not (> a b)
+    (abort (format "Version number segment too large to fit in the
+ version-code scheme: %s > %s, maximum version in each segment is %s"
+                   b a (str/join "." version-maximums))))
   b)

Given a version map containing :major :minor :patch :build and :priority version numbers, returns an integer which is guaranteed to be greater for semantically larger version numbers.

@@ -3655,24 +3843,33 @@ ((juxt :major :minor :patch :priority)) (map (fnil assert> 0 0) version-maximums) (map * version-coefficients) - (reduce +)))

If a :manifest-template-path is specified, perform template substitution with + (reduce +)))

Merges the main application manifest file with manifests from AAR files.

+
(defn merge-manifests
+  [{{:keys [manifest-path manifest-main-app-path]} :android :as project}]
+  (let [merger (ManifestMerger. (MergerLog/wrapSdkLog
+                                 (StdLogger. StdLogger$Level/VERBOSE)) nil)
+        lib-manifests (get-aar-files project "AndroidManifest.xml")]
+    (debug "Merging secondary manifests:" lib-manifests)
+    (.process merger (jio/file manifest-path) (jio/file manifest-main-app-path)
+              (into-array File lib-manifests) nil nil)))

If a :manifest-template-path is specified, perform template substitution with the values in :android :manifest, including the version-name and version-code which are automatically generated, placing the output in :manifest-path.

(defn generate-manifest
-  [{{:keys [manifest-path manifest-template-path manifest-options target-path
-            build-type]} :android, version :version :as project}]
+  [{{:keys [manifest-path manifest-template-path manifest-options manifest-main-app-path
+            target-version]} :android, version :version :as project}]
   (info "Generating manifest...")
   (let [full-manifest-map (merge {:version-name version
                                   :version-code (-> version
                                                     parse-semantic-version
                                                     version-code)
-                                  :debug-build (not build-type)}
+                                  :target-version target-version
+                                  :debug-build (dev-build? project)}
                                  manifest-options)]
-    (when (.exists (jio/file manifest-template-path))
-      (clojure.java.io/make-parents manifest-path)
-      (->> full-manifest-map
-           (clostache/render (slurp manifest-template-path))
-           (spit manifest-path)))))
 

Provides tasks for creating a new project or initialiaing plugin + (jio/make-parents manifest-path) + (->> full-manifest-map + (clostache/render (slurp manifest-template-path)) + (spit manifest-main-app-path)) + (merge-manifests project)))

 

Provides tasks for creating a new project or initialiaing plugin support in an existing one.

(ns leiningen.droid.new
   (:require [clojure.string :as string]
@@ -3680,8 +3877,7 @@
   (:use [leiningen.core.main :only [info abort]]
         [leiningen.new.templates :only [render-text slurp-resource
                                         sanitize ->files]]
-        [leiningen.droid.manifest :only [get-target-sdk-version
-                                         get-project-version]]))

Taken from lein-newnew.

+ [leiningen.droid.manifest :only [get-target-sdk-version]]))

Taken from lein-newnew.

Create a renderer function that looks for mustache templates in the right place given the name of your template. If no data is passed, the @@ -3694,18 +3890,11 @@ (render-text (slurp-resource res) data) (io/input-stream res)))))

(defn package-to-path [package-name]
-  (string/replace package-name #"\." "/"))

Loads a properties file. Returns nil if the file doesn't exist.

-
(defn- load-properties
-  [file]
-  (when (.exists file)
-    (with-open [rdr (io/reader file)]
-      (let [properties (java.util.Properties.)]
-        (.load properties rdr)
-        properties))))
+ (string/replace package-name #"\." "/"))
(defn package-name-valid? [package-name]
   (and (not (.startsWith package-name "."))
        (> (.indexOf package-name ".") -1)
-       (= (.indexOf package-name "-") -1)))

Creates project.clj file in an existing Android project folder.

+ (= (.indexOf package-name "-") -1)))

Creates project.clj file within an existing Android library folder.

Presumes default directory names (like src, res and gen) and AndroidManifest.xml file to be already present in the project.

@@ -3716,66 +3905,77 @@ (abort "ERROR: AndroidManifest.xml not found - have to be in an existing" "Android project. Use `lein droid new` to create a new project.")) (let [manifest-path (.getAbsolutePath manifest) - [_ name] (re-find #".*/(.+)/\." current-dir) - props (load-properties (io/file current-dir "project.properties")) - data {:name name - :version (or (get-project-version manifest-path) - "0.0.1-SNAPSHOT") - :target-sdk (or (get-target-sdk-version manifest-path) "10") - :library? (if (and props - (= (.getProperty props "android.library") - "true")) - ":library true" "")} + data {:name (.getName (io/file current-dir)) + :target-sdk (or (get-target-sdk-version manifest-path) "15")} render (renderer "templates")] (info "Creating project.clj...") (io/copy (render "library.project.clj" data) - (io/file current-dir "project.clj")))))

Creates new Android project given the project's name and package name.

-
(defn new
-  [project-name package-name & options]
-  (when-not (package-name-valid? package-name)
-    (abort "ERROR: Package name should have at least two levels and"
-           "not contain hyphens (you can replace them with underscores)."))
-  (let [options (apply hash-map options)
-        data {:name project-name
-              :package package-name
-              :package-sanitized (sanitize package-name)
-              :path (package-to-path (sanitize package-name))
-              :activity (get options ":activity" "MainActivity")
-              :target-sdk (get options ":target-sdk" "15")
-              :app-name (get options ":app-name" project-name)}
-        render (renderer "templates")]
+               (io/file current-dir "project.clj")))))

Creates new Android library.

+
(defn new-library
+  [library-name package-name data]
+  (let [render (renderer "templates")]
+    (info "Creating library" library-name "...")
+    (->files
+     data
+     "assets"
+     [".gitignore" (render "gitignore")]
+     ["LICENSE" (render "LICENSE")]
+     ["README.md" (render "README.library.md" data)]
+     ["AndroidManifest.template.xml" (render "AndroidManifest.library.xml" data)]
+     ["project.clj" (render "library.project.clj" data)]
+     ["res/values/strings.xml" (render "strings.library.xml" data)]
+     ["src/java/{{path}}/Util.java" (render "Util.java" data)]
+     ["src/clojure/{{path}}/main.clj" (render "core.clj" data)])))

Creates new Android application.

+
(defn new-application
+  [project-name package-name data]
+  (let [render (renderer "templates")]
     (info "Creating project" project-name "...")
     (->files
      data
      "assets"
      [".gitignore" (render "gitignore")]
      ["LICENSE" (render "LICENSE" data)]
+     ["README.md" (render "README.md" data)]
      ["AndroidManifest.template.xml" (render "AndroidManifest.template.xml" data)]
      ["project.clj" (render "project.clj" data)]
+     ["build/proguard-minify.cfg" (render "proguard_minify.cfg" data)]
+     ["build/proguard-multi-dex.cfg" (render "proguard_multi_dex.cfg" data)]
      ["res/drawable-hdpi/splash_circle.png" (render "splash_circle.png")]
      ["res/drawable-hdpi/splash_droid.png" (render "splash_droid.png")]
      ["res/drawable-hdpi/splash_hands.png" (render "splash_hands.png")]
      ["res/drawable-hdpi/ic_launcher.png" (render "ic_launcher_hdpi.png")]
      ["res/drawable-mdpi/ic_launcher.png" (render "ic_launcher_mdpi.png")]
-     ["res/drawable-ldpi/ic_launcher.png" (render "ic_launcher_ldpi.png")]
      ["res/drawable/splash_background.xml" (render "splash_background.xml")]
      ["res/anim/splash_rotation.xml" (render "splash_rotation.xml")]
      ["res/layout/splashscreen.xml" (render "splashscreen.xml")]
      ["res/values/strings.xml" (render "strings.xml" data)]
      ["src/java/{{path}}/SplashActivity.java" (render "SplashActivity.java" data)]
-     ["src/clojure/{{path}}/main.clj" (render "main.clj" data)])))
 

Functions to interact with Android SDK tools.

+ ["src/clojure/{{path}}/main.clj" (render "main.clj" data)])))

Creates new Android project given the project's name and package name.

+
(defn new
+  [project-name package-name & options]
+  (when-not (package-name-valid? package-name)
+    (abort "ERROR: Package name should have at least two levels and"
+           "not contain hyphens (you can replace them with underscores)."))
+  (let [options (apply hash-map options)
+        data {:name project-name
+              :package package-name
+              :package-sanitized (sanitize package-name)
+              :path (package-to-path (sanitize package-name))
+              :activity (get options ":activity" "MainActivity")
+              :target-sdk (get options ":target-sdk" "15")
+              :min-sdk (get options ":min-sdk" "15")
+              :app-name (get options ":app-name" project-name)
+              :library (get options ":library" false)
+              :new-project true}]
+    (if (= (:library data) "true")
+      (new-library project-name package-name data)
+      (new-application project-name package-name data))))
 

Functions to interact with Android SDK tools.

(ns leiningen.droid.sdk
-  (:use [leiningen.core.main :only [debug]])
-  (:require [cemerick.pomegranate :as pomegranate]
+  (:use [leiningen.core.main :only [debug abort]])
+  (:require [leiningen.droid.aar :refer [get-aar-native-paths]]
+            [leiningen.droid.sideload :as sideload]
             [clojure.java.io :as io])
-  (:import java.io.File java.io.PrintStream))

Uses reflection to make an ApkBuilder instance.

-
(defn- make-apk-builder
-  [apk-name res-path dex-path]
-  (let [apkbuilder-class (Class/forName "com.android.sdklib.build.ApkBuilder")
-        constructor (. apkbuilder-class getConstructor
-                       (into-array [File File File String PrintStream]))]
-    (.newInstance constructor (into-array [(io/file apk-name) (io/file res-path)
-                                           nil nil nil]))))

Returns paths to unpacked native libraries if they exist, nil otherwise.

+ (:import java.io.File))

Returns paths to unpacked native libraries if they exist, nil otherwise.

(defn- get-unpacked-natives-paths
   []
   (let [path "target/native/linux/"]
@@ -3783,12 +3983,12 @@
       [path])))

Delegates APK creation to ApkBuilder class in sdklib.jar.

(defn create-apk
   [{{:keys [sdk-path out-res-pkg-path out-dex-path native-libraries-paths]}
-    :android} & {:keys [apk-name resource-jars]}]
-  ;; Dynamically load sdklib.jar
-  (pomegranate/add-classpath (io/file sdk-path "tools" "lib" "sdklib.jar"))
-  (let [apkbuilder (make-apk-builder apk-name out-res-pkg-path out-dex-path)
+    :android :as project} & {:keys [apk-name resource-jars]}]
+  (sideload/sideload-jars sdk-path)
+  (let [apkbuilder (sideload/apk-builder apk-name out-res-pkg-path out-dex-path)
         all-native-libraries (concat native-libraries-paths
-                                     (get-unpacked-natives-paths))
+                                     (get-unpacked-natives-paths)
+                                     (get-aar-native-paths project))
         dexes (filter #(re-matches #".*dex" (.getName %))
                       (.listFiles (io/file out-dex-path)))]
     (when (seq resource-jars)
@@ -3799,18 +3999,77 @@
       (debug "Adding native libraries: " all-native-libraries)
       (doseq [lib all-native-libraries]
         (.addNativeLibraries apkbuilder ^File (io/file lib))))
-    (when (seq dexes)
-      (debug "Adding DEX files: " dexes)
-      (doseq [dex dexes]
-        (.addFile apkbuilder dex (.getName dex))))
-    (.sealApk apkbuilder)))
 

Provides utilities for the plugin.

+ (if (seq dexes) + (do + (debug "Adding DEX files: " dexes) + (doseq [dex dexes] + (.addFile apkbuilder dex (.getName dex)))) + (abort "No *.dex files found in " out-dex-path)) + (.sealApk apkbuilder)))
 

Wrappers around classes and methods that we pull dynamically from jars in + Android SDK.

+
(ns leiningen.droid.sideload
+  (:require [cemerick.pomegranate :as cp]
+            [clojure.java.io :as io])
+  (:import [java.io File PrintStream]))

Dynamically adds jars from Android SDK on the classpath.

+
(def sideload-jars
+  (memoize (fn [sdk-path]
+             (cp/add-classpath (io/file sdk-path "tools" "lib" "sdklib.jar")))))

Uses reflection to make an ApkBuilder instance.

+
(defn apk-builder
+  [apk-name res-path dex-path]
+  (let [apkbuilder-class (Class/forName "com.android.sdklib.build.ApkBuilder")
+        constructor (. apkbuilder-class getConstructor
+                       (into-array [File File File String PrintStream]))]
+    (.newInstance constructor (into-array [(io/file apk-name) (io/file res-path)
+                                           nil nil nil]))))

Uses reflection to make an SymbolLoader instance.

+
(defn symbol-loader
+  [file]
+  (let [sl-class (Class/forName "com.android.sdklib.internal.build.SymbolLoader")
+        constructor (. sl-class getConstructor (into-array [File]))]
+    (.newInstance constructor (into-array [(io/file file)]))))

Uses reflection to make an SymbolLoader instance.

+
(defn symbol-writer
+  [out-folder package-name full-symbols]
+  (let [sl-class (Class/forName "com.android.sdklib.internal.build.SymbolLoader")
+        sw-class (Class/forName "com.android.sdklib.internal.build.SymbolWriter")
+        constructor (. sw-class getConstructor (into-array [String String sl-class]))]
+    (.newInstance constructor (into-array Object [out-folder package-name full-symbols]))))
 
+
(ns leiningen.droid.test
+  (:refer-clojure :exclude [test])
+  (:require [bultitude.core :as b]
+            [clojure.java.io :as io]
+            [clojure.string :as str]
+            [clojure.set :as set]
+            [leiningen.core.classpath :as cp]
+            [leiningen.droid.code-gen :as code-gen]
+            [leiningen.droid.compile :as compile]
+            [leiningen.droid.utils :as utils]))

Runs tests locally using Robolectric.

+
(defn local-test
+  [{{:keys [cloverage-exclude-ns]} :android :as project} & [mode]]
+  (when-not (-> project :android :library)
+    (code-gen/code-gen project))
+  (compile/compile project)
+  (let [src-nses (b/namespaces-on-classpath
+                  :classpath (map io/file (distinct (:source-paths project)))
+                  :ignore-unreadable? false)
+        src-nses (set/difference (set src-nses)
+                                 (set (map symbol cloverage-exclude-ns)))
+        test-nses (b/namespaces-on-classpath
+                   :classpath (map io/file (distinct (:test-paths project)))
+                   :ignore-unreadable? false)
+        cpath (cp/get-classpath project)
+        mode (or mode "clojuretest")]
+    (binding [utils/*sh-print-output* true]
+      (utils/sh "java" "-cp" (str/join ":" cpath)
+                "coa.droid_test.internal.TestRunner" "-mode" mode
+                ":src" (map str src-nses)
+                ":test" (map str test-nses)))))
 

Provides utilities for the plugin.

(ns leiningen.droid.utils
-  (:require [leiningen.core.project :as pr])
+  (:require [leiningen.core.project :as pr]
+            [robert.hooke :refer [with-hooks-disabled]])
   (:use [clojure.java.io :only (file reader)]
-        [leiningen.core.main :only (info debug abort)]
+        [leiningen.core.main :only (info debug abort *debug*)]
         [leiningen.core.classpath :only [resolve-dependencies]]
         [clojure.string :only (join)])
-  (:import java.io.File))

Convenient functions to run SDK binaries

+ (:import [java.io File StringWriter]))

Convenient functions to run SDK binaries

Checks if the given directories or files exist. Aborts Leiningen execution in case either of them doesn't or the value equals nil.

@@ -3830,46 +4089,46 @@ (abort "The path" ~p "doesn't exist. Abort execution.")))))

Returns true if we are running on Microsoft Windows

(defn windows?
   []
-  (= java.io.File/separator "\\"))

Returns a map of relative paths to different SDK binaries for both + (= java.io.File/separator "\\"))

Returns a path to the correct Android Build Tools directory.

+
(defn get-sdk-build-tools-path
+  ([{{:keys [sdk-path build-tools-version]} :android}]
+   (get-sdk-build-tools-path sdk-path build-tools-version))
+  ([sdk-path build-tools-version]
+   (let [bt-root-dir (file sdk-path "build-tools")
+         ;; build-tools directory contains a subdir which name we don't
+         ;; know that has all the tools. Let's grab the last directory
+         ;; inside build-tools/ and hope it is the one we need.
+         bt-dir (or build-tools-version
+                    (->> (.list bt-root-dir)
+                         (filter #(.isDirectory (file bt-root-dir %)))
+                         sort last)
+                    (abort "Build tools not found."
+                           "Download them using the Android SDK Manager."))]
+     (file bt-root-dir bt-dir))))

Returns a map of relative paths to different SDK binaries for both Unix and Windows platforms.

(defn sdk-binary-paths
   [sdk-path build-tools-version]
   (ensure-paths sdk-path)
-  (let [bt-root-dir (file sdk-path "build-tools")
-        ;; build-tools directory contains a subdir which name we don't
-        ;; know that has all the tools. Let's grab the first directory
-        ;; inside build-tools/ and hope it is the one we need.
-        bt-dir (or build-tools-version
-                   (->> (.list bt-root-dir)
-                        (filter #(.isDirectory (file bt-root-dir %)))
-                        sort last)
-                   (abort "Build tools not found."
-                          "Download them using the Android SDK Manager."))
-        bt-ver (Integer/parseInt (get (re-find #"(\d+)\..*" bt-dir) 1 "-1"))]
-    ;; if bt-ver is non-negative we have a definite numeric version number
-    ;; assume the latest build-tools dir is not empty
-    {:dx {:unix ["build-tools" bt-dir "dx"]
-          :win ["build-tools" bt-dir "dx.bat"]}
-     :adb {:unix ["platform-tools" "adb"]
-           :win ["platform-tools" "adb.exe"]}
-     :aapt {:unix ["build-tools" bt-dir "aapt"]
-            :win ["build-tools" bt-dir "aapt.exe"]}
-     :zipalign (if (>= bt-ver 20)
-                 {:unix ["build-tools" bt-dir "zipalign"]
-                  :win ["build-tools" bt-dir "zipalign.exe"]}
-                 {:unix ["tools" "zipalign"]
-                  :win ["tools" "zipalign.exe"]})
-     :proguard {:unix ["tools" "proguard" "lib" "proguard.jar"]
-                :win ["tools" "proguard" "lib" "proguard.jar"]}}))

Given the project map and the binary keyword, returns either a full + (let [build-tools (get-sdk-build-tools-path sdk-path build-tools-version)] + {:dx {:unix (file build-tools "dx") + :win (file build-tools "dx.bat")} + :adb {:unix (file sdk-path "platform-tools" "adb") + :win (file sdk-path "platform-tools" "adb.exe")} + :aapt {:unix (file build-tools "aapt") + :win (file build-tools "aapt.exe")} + :zipalign {:unix (file build-tools "zipalign") + :win (file build-tools "zipalign.exe")} + :proguard {:unix (file sdk-path "tools" "proguard" "bin" "proguard.sh") + :win (file sdk-path "tools" "proguard" "bin" "proguard.bat")}}))

Given the project map and the binary keyword, returns either a full path to the binary as a string, or a vector with call to cmd.exe for batch-files.

(defn sdk-binary
   [{{:keys [sdk-path build-tools-version]} :android} binary-kw]
-  (let [binary (get-in (sdk-binary-paths sdk-path build-tools-version)
-                       [binary-kw (if (windows?) :win :unix)])
-        binary-str (str (apply file sdk-path binary))]
+  (let [binary-str (-> (sdk-binary-paths sdk-path build-tools-version)
+                       (get-in [binary-kw (if (windows?) :win :unix)])
+                       str)]
     (ensure-paths binary-str)
-    (if (.endsWith (last binary) ".bat")
+    (if (.endsWith binary-str ".bat")
       ["cmd.exe" "/C" binary-str]
       binary-str)))

Middleware section

Taken from Leiningen source code.

@@ -3896,29 +4155,29 @@
(defn get-default-android-params
   [{root :root, name :name, target-path :target-path
     java-paths :java-source-paths}]
-  (let [manifest-template "AndroidManifest.template.xml"
-        manifest-template-file (file (absolutize root manifest-template))
-        gen-path (str (file target-path "gen"))
-        has-template (.exists manifest-template-file)]
-    {:out-dex-path target-path
-     :manifest-path (if has-template
-                      (str (file target-path "AndroidManifest.xml"))
-                      "AndroidManifest.xml")
-     :manifest-template-path manifest-template
-     :manifest-options {:app-name "@string/app_name"}
-     :res-path "res"
-     :gen-path gen-path
-     :out-res-path (str (file target-path "res"))
-     :assets-paths ["assets"]
-     :assets-gen-path (str (file target-path "assets-gen"))
-     :out-res-pkg-path (str (file target-path (str name ".ap_")))
-     :out-apk-path (str (file target-path (str name ".apk")))
-     :keystore-path (str (file (System/getProperty "user.home")
-                               ".android" "debug.keystore"))
-     :key-alias "androiddebugkey"
-     :repl-device-port 9999
-     :repl-local-port 9999
-     :target-version 10}))
+ {:out-dex-path target-path + :proguard-execute false + :proguard-conf-path "proguard.conf" + :proguard-output-jar-path (file target-path "mininified-classes.jar") + :multi-dex-root-classes-path (file target-path "root-classes.jar") + :multi-dex-main-dex-list-path (file target-path "main-dex-list.txt") + :manifest-path (file target-path "AndroidManifest.xml") + :manifest-main-app-path (file target-path "AndroidManifest.app.xml") + :manifest-template-path "AndroidManifest.template.xml" + :manifest-options {:app-name "@string/app_name"} + :res-path "res" + :gen-path (file target-path "gen") + :out-res-path (file target-path "res") + :assets-paths ["assets"] + :assets-gen-path (file target-path "assets-gen") + :out-res-pkg-path (file target-path (str name ".ap_")) + :out-apk-path (file target-path (str name ".apk")) + :keystore-path (file (System/getProperty "user.home") + ".android" "debug.keystore") + :key-alias "androiddebugkey" + :repl-device-port 9999 + :repl-local-port 9999 + :target-version 15})
(declare android-parameters)

Reads and initializes a Leiningen project and applies Android middleware to it.

(defn read-project
@@ -3930,38 +4189,16 @@
   (let [project-directory (file project-directory-path)]
     (if (.isAbsolute project-directory)
       (file project-directory-path "project.clj")
-      (file root project-directory-path "project.clj"))))

Parses project.clj files from the project dependencies to extract - the paths to external resources and class files.

-
(defn process-project-dependencies
-  [{{:keys [project-dependencies]} :android, root :root :as project}]
-  (reduce (fn [project dependency-path]
-            (let [project-file (get-project-file root dependency-path)]
-              (if-not (.exists ^File project-file)
-                (do
-                  (info "WARNING:" (str project-file) "doesn't exist.")
-                  project)
-                (let [dep (read-project project-file)
-                      {:keys [compile-path dependencies]} dep
-                      {:keys [res-path out-res-path]} (:android dep)]
-                  (-> project
-                      (update-in [:dependencies]
-                                 concat dependencies)
-                      (update-in [:android :external-classes-paths]
-                                 conj compile-path)
-                      (update-in [:android :external-res-paths]
-                                 conj res-path out-res-path))))))
-          project project-dependencies))

Merges project's :android map with the default parameters map, - processes project dependencies and absolutizes paths in the - :android map.

- -

This is the middleware function to be plugged into project.clj.

+ (file root project-directory-path "project.clj"))))

Merges project's :android map with default Android parameters and + absolutizes paths in the :android map.

(defn android-parameters
   [{:keys [android] :as project}]
-  (let [android-params (merge (get-default-android-params project)
-                              android)]
+  (let [android-params (merge (get-default-android-params project) android)]
     (-> project
+        (vary-meta assoc-in [:profiles ::extras]
+                   {:java-source-paths [(:gen-path android-params)]})
+        (pr/merge-profiles [::extras])
         (assoc :android android-params)
-        process-project-dependencies
         absolutize-android-paths)))

General utilities

(defn proj [] (read-project "sample/project.clj"))

If version keyword is passed (for example, :ics or :jelly-bean), resolves @@ -3981,53 +4218,27 @@ (str (file sdk-root "platforms" (str "android-" (sdk-version-number version)))))

Returns a version-specific path to the android.jar SDK file.

(defn get-sdk-android-jar
-  [sdk-root version]
-  (str (file (get-sdk-platform-path sdk-root version) "android.jar")))

Returns a version-specific path to the Google SDK directory.

-
(defn get-sdk-google-api-path
-  [sdk-root version]
-  (str (file sdk-root "add-ons" (str "addon-google_apis-google-"
-                                     (sdk-version-number version)))))

Returns a version-specific paths to all Google SDK jars.

-
(defn get-sdk-google-api-jars
-  [sdk-root version]
-  (map #(.getAbsolutePath ^File %)
-       (rest ;; The first file is the directory itself, no need in it.
-        (file-seq
-         (file (str (get-sdk-google-api-path sdk-root version) "/libs"))))))

Returns a path to the Android Support library.

-
(defn- get-sdk-support-jar
-  [sdk-root version]
-  (.getAbsolutePath
-   (apply file sdk-root "extras" "android" "support"
-          (case version
-            "v4"             ["v4" "android-support-v4.jar"]
-            "v7-appcompat"   ["v7" "appcompat" "libs"
-                              "android-support-v7-appcompat.jar"]
-            "v7-gridlayout"  ["v7" "gridlayout" "libs"
-                              "android-support-v7-gridlayout.jar"]
-            "v7-mediarouter" ["v7" "mediarouter" "libs"
-                              "android-support-v7-mediarouter.jar"]
-            "v13"            ["v13" "android-support-v13.jar"]
-            (abort "Unknown support library version in :support-libraries : "
-                   version)))))

Takes a list of support library versions and returns a list of JAR - files.

-
(defn get-sdk-support-jars
-  [sdk-root version-list & [warn?]]
-  (let [message "WARNING: Support library V4 is redundant if you use V13."
-        versions (set version-list)
-        versions (if (every? versions ["v4" "v13"])
-                   (do (when warn? (info message))
-                       (disj versions "v4"))
-                   versions)]
-    (map #(get-sdk-support-jar sdk-root %) (seq versions))))

Get the list of dependency libraries that has :use-resources true + ([{{:keys [sdk-path target-version]} :android :as project}] + (get-sdk-android-jar sdk-path target-version)) + ([sdk-root version] + (str (file (get-sdk-platform-path sdk-root version) "android.jar"))))

Returns a path to annotations.jar file.

+
(defn get-sdk-annotation-jar
+  [sdk-root-or-project]
+  (let [sdk-root (if (map? sdk-root-or-project)
+                   (get-in sdk-root-or-project [:android :sdk-path])
+                   sdk-root-or-project)]
+    (str (file sdk-root "tools" "support" "annotations.jar"))))

Get the list of dependency libraries that has :use-resources true in their definition.

(defn get-resource-jars
   [{:keys [dependencies] :as project}]
-  (let [res-deps (for [[lib _ & options :as dep] (:dependencies project)
-                       :when (or (:use-resources (apply hash-map options))
-                                 ;; Should be removed in final release
-                                 (= lib 'org.clojure-android/clojure))]
-                   dep)
+  (let [res-deps (filter (fn [[_ _ & {:as options}]]
+                           (:use-resources options))
+                         (:dependencies project))
         mod-proj (assoc project :dependencies res-deps)]
-    (resolve-dependencies :dependencies mod-proj)))

Executes the subprocess specified in the binding list and applies + (with-hooks-disabled resolve-dependencies + (resolve-dependencies :dependencies mod-proj))))

If true, print the output of the shell command regardless of debug.

+
(def ^:dynamic *sh-print-output*
+  false)

Executes the subprocess specified in the binding list and applies body do it while it is running. The binding list consists of a var name for the process and the list of strings that represents shell command.

@@ -4043,20 +4254,30 @@ (let [builder# (ProcessBuilder. ~command) _# (.redirectErrorStream builder# true) ~process-name (.start builder#) - output# (line-seq (reader (.getInputStream ~process-name)))] + output# (line-seq (reader (.getInputStream ~process-name))) + out-stream# (StringWriter.) + print-output?# (or *debug* *sh-print-output*)] ~@body - (.waitFor ~process-name) (doseq [line# output#] - (if (= (.exitValue ~process-name) 0) - (debug line#) - (info line#))) + (if print-output?# + (info line#) + (binding [*out* out-stream#] + (println line#)))) + (.waitFor ~process-name) + (when-not (and (= (.exitValue ~process-name) 0) + (not print-output?#)) + (info (str out-stream#))) (when-not (= (.exitValue ~process-name) 0) (abort "Abort execution.")) output#)))

Executes the command given by args in a subprocess. Flattens the - given list.

+ given list. Turns files into canonical paths.

(defn sh
   [& args]
-  (with-process [process (flatten args)]))

Checks the build type of the current project, assuming dev build if + (let [str-args (for [arg (flatten args)] + (if (instance? File arg) + (.getCanonicalPath ^File arg) + (str arg)))] + (with-process [process str-args])))

Checks the build type of the current project, assuming dev build if not a release build

(defn dev-build?
   [project]
@@ -4108,7 +4329,7 @@
       "-dname" "CN=Android Debug,O=Android,C=US"))
(defn relativize-path [^File dir ^File to-relativize]
   (.getPath (.relativize (.toURI dir)
-                         (.toURI to-relativize))))