diff options
Diffstat (limited to 'lux-lein/src/leiningen')
-rw-r--r-- | lux-lein/src/leiningen/luxc.clj | 27 | ||||
-rw-r--r-- | lux-lein/src/leiningen/luxc/compiler.clj | 19 | ||||
-rw-r--r-- | lux-lein/src/leiningen/luxc/packager.clj | 212 | ||||
-rw-r--r-- | lux-lein/src/leiningen/luxc/repl.clj | 35 | ||||
-rw-r--r-- | lux-lein/src/leiningen/luxc/test.clj | 27 | ||||
-rw-r--r-- | lux-lein/src/leiningen/luxc/utils.clj | 97 |
6 files changed, 417 insertions, 0 deletions
diff --git a/lux-lein/src/leiningen/luxc.clj b/lux-lein/src/leiningen/luxc.clj new file mode 100644 index 000000000..b04e4997a --- /dev/null +++ b/lux-lein/src/leiningen/luxc.clj @@ -0,0 +1,27 @@ +;; Copyright (c) Eduardo Julian. All rights reserved. +;; This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +;; If a copy of the MPL was not distributed with this file, +;; You can obtain one at http://mozilla.org/MPL/2.0/. + +(ns leiningen.luxc + (:require [leiningen.pom :as pom] + [leiningen.core.classpath :as classpath] + (leiningen.luxc [compiler :as &compiler] + [test :as &test] + [repl :as &repl]))) + +;; [Exports] +(defn luxc [project & args] + (case (first args) + "compile" + (&compiler/compile project) + + "test" + (&test/test project) + + "repl" + (&repl/repl project) + + ;; default... + (println "Commands available: compile, test, repl")) + ) diff --git a/lux-lein/src/leiningen/luxc/compiler.clj b/lux-lein/src/leiningen/luxc/compiler.clj new file mode 100644 index 000000000..18eef63a0 --- /dev/null +++ b/lux-lein/src/leiningen/luxc/compiler.clj @@ -0,0 +1,19 @@ +;; Copyright (c) Eduardo Julian. All rights reserved. +;; This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +;; If a copy of the MPL was not distributed with this file, +;; You can obtain one at http://mozilla.org/MPL/2.0/. + +(ns leiningen.luxc.compiler + (:refer-clojure :exclude [compile]) + (:require [leiningen.core.classpath :as classpath] + (leiningen.luxc [utils :as &utils] + [packager :as &packager]))) + +(defn compile [project] + (if-let [program-module (get-in project [:lux :program])] + (do (&utils/run-process (&utils/compile-path project program-module (get project :source-paths (list))) + nil + "[COMPILATION BEGIN]" + "[COMPILATION END]") + (&packager/package project program-module (get project :resource-paths (list)))) + (println "Please provide a program main module in [:lux :program]"))) diff --git a/lux-lein/src/leiningen/luxc/packager.clj b/lux-lein/src/leiningen/luxc/packager.clj new file mode 100644 index 000000000..e7b1d71d8 --- /dev/null +++ b/lux-lein/src/leiningen/luxc/packager.clj @@ -0,0 +1,212 @@ +;; Copyright (c) Eduardo Julian. All rights reserved. +;; This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +;; If a copy of the MPL was not distributed with this file, +;; You can obtain one at http://mozilla.org/MPL/2.0/. + +(ns leiningen.luxc.packager + (:require [clojure.string :as string] + [leiningen.core.classpath :as classpath] + [leiningen.uberjar] + [leiningen.luxc.utils :as &utils]) + (:import (java.io InputStream + File + FileInputStream + FileOutputStream + BufferedInputStream + ByteArrayInputStream + ByteArrayOutputStream) + (java.util.jar Manifest + Attributes$Name + JarEntry + JarInputStream + JarOutputStream + ))) + +;; [Utils] +(def ^:private kilobyte 1024) +(def ^:private buffer-size (* 10 kilobyte)) + +(defn ^:private manifest + "(-> Project Text Bool Manifest)" + [project module includes-android?] + (doto (new Manifest) + (-> .getMainAttributes + (doto (-> (.put Attributes$Name/MAIN_CLASS (str module "._")) + (->> (when (not includes-android?)))) + (.put Attributes$Name/MANIFEST_VERSION "1.0") + (.put (new Attributes$Name "LUX_JAR") "true") + (-> (.put (new Attributes$Name name) real-v) + (->> (doseq [[name v] (get project :manifest) + :let [real-v (if (string? v) v (v project))]]))))))) + +(defn ^:private write-class! + "(-> Text File JarOutputStream Null)" + [^String path ^File file ^JarOutputStream out] + (with-open [in (new BufferedInputStream (new FileInputStream file))] + (let [buffer (byte-array buffer-size)] + (doto out + (.putNextEntry (new JarEntry (str path "/" (.getName file)))) + (-> (.write buffer 0 bytes-read) + (->> (when (not= -1 bytes-read)) + (loop [bytes-read (.read in buffer)]))) + (.flush) + (.closeEntry) + )) + )) + +(defn ^:private write-module! + "(-> File JarOutputStream Null)" + [^File file ^JarOutputStream out output-dir] + (let [output-dir-size (inc (.length output-dir)) + module-name (.substring (.getPath file) output-dir-size) + inner-files (.listFiles file) + inner-modules (filter #(.isDirectory ^File %) inner-files) + inner-classes (filter #(not (.isDirectory ^File %)) inner-files)] + (doseq [$class inner-classes] + (write-class! module-name $class out)) + (doseq [$module inner-modules] + (write-module! $module out output-dir)))) + +(defn ^:private write-resources! + "(-> JarOutputStream (List Text) Null)" + [^JarOutputStream out resources-dirs] + (doseq [resources-dir resources-dirs + :let [resources-dir (new File resources-dir)] + :when (.exists resources-dir) + ^File res (.listFiles resources-dir) + :let [buffer (byte-array buffer-size)]] + (with-open [in (->> res (new FileInputStream) (new BufferedInputStream))] + (doto out + (.putNextEntry (new JarEntry (.getName res))) + (-> (.write buffer 0 bytes-read) + (->> (when (not= -1 bytes-read)) + (loop [bytes-read (.read in buffer)]))) + (.flush) + (.closeEntry)) + ))) + +(let [init-capacity (* 100 1024) + buffer-size 1024] + (defn ^:private ^"[B" read-stream [^InputStream is] + (let [buffer (byte-array buffer-size)] + (with-open [os (new ByteArrayOutputStream init-capacity)] + (loop [bytes-read (.read is buffer 0 buffer-size)] + (when (not= -1 bytes-read) + (do (.write os buffer 0 bytes-read) + (recur (.read is buffer 0 buffer-size))))) + (.toByteArray os))))) + +(defn ^:private add-jar! [^File jar-file project !all-jar-files] + (with-open [is (->> jar-file (new FileInputStream) (new JarInputStream))] + (loop [^JarEntry entry (.getNextJarEntry is)] + (when entry + (let [entry-name (.getName entry)] + (if (and (not (.isDirectory entry)) + (not (.startsWith entry-name "META-INF/maven/")) + (not (some (fn [exclusion] + (re-find exclusion entry-name)) + (get project :uberjar-exclusions)))) + (let [entry-data (read-stream is) + entry-data (or (some (fn [[pattern [read fuse write]]] + (let [matches? (if (string? pattern) + (= pattern entry-name) + (re-find pattern entry-name))] + (when matches? + (let [os (new ByteArrayOutputStream 1024) + [_data _entry] (get @!all-jar-files entry-name [(byte-array 0) nil]) + _ (write os (fuse (read (new ByteArrayInputStream _data)) + (read (new ByteArrayInputStream entry-data))))] + (.toByteArray os))))) + (eval (get project :uberjar-merge-with))) + entry-data)] + (swap! !all-jar-files assoc entry-name [entry-data entry]) + (recur (.getNextJarEntry is))) + (recur (.getNextJarEntry is)))) + )))) + +(def default-manifest-file "./AndroidManifest.xml") + +;; [Resources] +(defn package + "(-> Text (List Text) Null)" + [project module resources-dirs] + (let [output-dir (get-in project [:lux :target] &utils/output-dir) + output-package (str (get-in project [:lux :target] &utils/output-dir) "/" + (get project :jar-name &utils/output-package)) + !all-jar-files (atom {}) + includes-android? (boolean (some #(-> % first (= 'com.google.android/android)) + (get project :dependencies))) + project* (-> project + (update-in [:dependencies] (fn [_deps] + ;; Skip the last two, + ;; because they are: + ;; tools.nrepl-0.2.12.jar and + ;; clojure-complete-0.2.4.jar + ;; and they belong to Leiningen. + (take (- (count _deps) 2) _deps)))) + deps (->> project* + (classpath/resolve-managed-dependencies :dependencies :managed-dependencies) + (map #(.getAbsolutePath ^File %)))] + (do (.delete (new File output-package)) + (with-open [out (new JarOutputStream + (->> output-package (new File) (new FileOutputStream)) + (manifest project module includes-android?))] + (do (doseq [$group (.listFiles (new File output-dir))] + (write-module! $group out output-dir)) + (when (not (get-in project [:lux :android])) + (write-resources! out resources-dirs)) + (doseq [^String file-path deps] + (add-jar! (new File file-path) project !all-jar-files)) + (doseq [[_ [entry-data entry]] @!all-jar-files] + (doto out + (.putNextEntry (doto entry (.setCompressedSize -1))) + (.write entry-data 0 (alength entry-data)) + (.flush) + (.closeEntry))) + nil)) + (when (get-in project [:lux :android]) + (let [output-dex "classes.dex" + _ (do (.delete (new File output-dex)) + (&utils/run-process (str "dx --dex --output=" output-dex " " output-package) + (new File (get-in project [:lux :target] &utils/output-dir)) + "[DX BEGIN]" + "[DX END]")) + manifest-path (get-in project [:lux :android :manifest] default-manifest-file) + sdk-path (get-in project [:lux :android :sdk]) + android-path (str sdk-path "/platforms/android-" (get-in project [:lux :android :version]) "/android.jar") + _ (assert (.exists (new File android-path)) + (str "Can't find Android JAR: " android-path)) + output-apk-unaligned (string/replace output-package #"\.jar$" ".apk.unaligned") + output-apk (string/replace output-package #"\.jar$" ".apk") + current-working-dir (.getCanonicalPath (new File ".")) + _ (do (&utils/run-process (str "aapt package -f -M " manifest-path " -I " android-path " -F " output-apk-unaligned + (apply str " " (interleave (repeat (count resources-dirs) + "-A ") + (filter #(.exists (new File %)) + resources-dirs))) + (apply str " " (interleave (repeat (count resources-dirs) + "-S ") + (->> (get-in project [:lux :android :resources] ["android-resources"]) + (map (partial str current-working-dir "/")) + (filter #(.exists (new File %))))))) + nil + "[AAPT PACKAGE BEGIN]" + "[AAPT PACKAGE END]") + (&utils/run-process (str "aapt add -f " output-apk-unaligned " " output-dex) + (new File (get-in project [:lux :target] &utils/output-dir)) + "[AAPT ADD BEGIN]" + "[AAPT ADD END]") + (when-let [path (get-in project [:lux :android :keystore :path])] + (when-let [alias (get-in project [:lux :android :keystore :alias])] + (when-let [password (get-in project [:lux :android :keystore :password])] + (&utils/run-process (str "jarsigner -storepass " password " -keystore " path " " output-apk-unaligned " " alias) + nil + "[JARSIGNER BEGIN]" + "[JARSIGNER END]")))) + (&utils/run-process (str "zipalign 4 " output-apk-unaligned " " output-apk) + nil + "[ZIPALIGN BEGIN]" + "[ZIPALIGN END]") + ) + ] + nil))))) diff --git a/lux-lein/src/leiningen/luxc/repl.clj b/lux-lein/src/leiningen/luxc/repl.clj new file mode 100644 index 000000000..2bfb281e6 --- /dev/null +++ b/lux-lein/src/leiningen/luxc/repl.clj @@ -0,0 +1,35 @@ +;; Copyright (c) Eduardo Julian. All rights reserved. +;; This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +;; If a copy of the MPL was not distributed with this file, +;; You can obtain one at http://mozilla.org/MPL/2.0/. + +(ns leiningen.luxc.repl + (:require [leiningen.core.classpath :as classpath] + [leiningen.luxc.utils :as &utils]) + (:import (java.io InputStreamReader + BufferedReader + PrintStream))) + +(defn repl [project] + (println (&utils/repl-path project (:source-paths project))) + ;; (let [process (.exec (Runtime/getRuntime) (&utils/repl-path project (:source-paths project)))] + ;; (with-open [std-in (->> System/in (new InputStreamReader) (new BufferedReader)) + ;; process-in (->> process .getOutputStream (new PrintStream)) + ;; process-out (->> process .getInputStream (new InputStreamReader) (new BufferedReader)) + ;; process-err (->> process .getErrorStream (new InputStreamReader) (new BufferedReader))] + ;; (loop [] + ;; (do (loop [] + ;; (when (.ready process-out) + ;; (println (.readLine process-out)) + ;; (recur))) + ;; (loop [had-error? false] + ;; (if (.ready process-out) + ;; (do (println (.readLine process-err)) + ;; (recur true)) + ;; (when had-error? + ;; (System/exit 1)))) + ;; (when-let [input (.readLine std-in)] + ;; (do (.println process-in input) + ;; (recur))))) + ;; )) + ) diff --git a/lux-lein/src/leiningen/luxc/test.clj b/lux-lein/src/leiningen/luxc/test.clj new file mode 100644 index 000000000..a1b5a830c --- /dev/null +++ b/lux-lein/src/leiningen/luxc/test.clj @@ -0,0 +1,27 @@ +;; Copyright (c) Eduardo Julian. All rights reserved. +;; This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +;; If a copy of the MPL was not distributed with this file, +;; You can obtain one at http://mozilla.org/MPL/2.0/. + +(ns leiningen.luxc.test + (:refer-clojure :exclude [test]) + (:require [leiningen.core.classpath :as classpath] + (leiningen.luxc [utils :as &utils] + [packager :as &packager]))) + +(defn test [project] + (if-let [tests-module (get-in project [:lux :tests])] + (do (&utils/run-process (&utils/compile-path project tests-module (concat (:test-paths project) (:source-paths project))) + nil + "[COMPILATION BEGIN]" + "[COMPILATION END]") + (let [java-cmd (get project :java-cmd "java") + jvm-opts (->> (get project :jvm-opts) (interpose " ") (reduce str "")) + output-package (str (get-in project [:lux :target] &utils/output-dir) "/" + (get project :jar-name &utils/output-package))] + (do (&packager/package project tests-module (get project :resource-paths (list))) + (&utils/run-process (str java-cmd " " jvm-opts " -jar " output-package) + nil + "[TEST BEGIN]" + "[TEST END]")))) + (println "Please provide a test module in [:lux :tests]"))) diff --git a/lux-lein/src/leiningen/luxc/utils.clj b/lux-lein/src/leiningen/luxc/utils.clj new file mode 100644 index 000000000..bae02d365 --- /dev/null +++ b/lux-lein/src/leiningen/luxc/utils.clj @@ -0,0 +1,97 @@ +;; Copyright (c) Eduardo Julian. All rights reserved. +;; This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +;; If a copy of the MPL was not distributed with this file, +;; You can obtain one at http://mozilla.org/MPL/2.0/. + +(ns leiningen.luxc.utils + (:refer-clojure :exclude [compile]) + (:require [leiningen.core.classpath :as classpath]) + (:import (java.io File + InputStreamReader + BufferedReader))) + +(def ^:const ^String output-dir "target/jvm") +(def ^:const ^String output-package "program.jar") + +(def ^:private unit-separator (str (char 31))) + +(def ^:private vm-options "-server -Xms2048m -Xmx2048m -XX:+OptimizeStringConcat") + +(defn compile-path [project module source-paths] + (let [output-dir (get-in project [:lux :target] output-dir) + jar-paths (->> ^java.net.URLClassLoader (ClassLoader/getSystemClassLoader) + (.getURLs) + (map #(.getFile ^java.net.URL %)) + (filter #(.endsWith ^String % ".jar"))) + compiler-path (some (fn [^:private path] + (if (.contains path "com/github/luxlang/luxc-jvm") + path + nil)) + jar-paths) + stdlib-path (some (fn [^:private path] + (if (.contains path "com/github/luxlang/lux-stdlib") + path + nil)) + jar-paths) + deps-paths (filter (fn [^:private path] + (or (.contains path "org/ow2/asm/asm-all") + (.contains path "org/clojure/core.match") + (.contains path "org/clojure/clojure"))) + jar-paths) + sdk-path (get-in project [:lux :android :sdk]) + android-path (str sdk-path "/platforms/android-" (get-in project [:lux :android :version]) "/android.jar") + deps-paths (if (.exists (new File android-path)) + (cons android-path deps-paths) + deps-paths)] + (let [class-path (->> (classpath/get-classpath project) + (filter #(.endsWith % ".jar")) + (concat deps-paths) + (list* stdlib-path) + (interpose java.io.File/pathSeparator) + (reduce str "")) + java-cmd (get project :java-cmd "java") + jvm-opts (->> (get project :jvm-opts) (interpose " ") (reduce str ""))] + (str java-cmd " " jvm-opts " " vm-options " -cp " (str compiler-path ":" class-path) + " lux release " module + " " (->> (get project :resource-paths (list)) (interpose unit-separator) (apply str)) + " " (->> source-paths (interpose unit-separator) (apply str)) + " " output-dir)))) + +(defn repl-path [project source-paths] + (let [jar-paths (->> ^java.net.URLClassLoader (ClassLoader/getSystemClassLoader) + (.getURLs) + (map #(.getFile ^java.net.URL %)) + (filter #(.endsWith ^String % ".jar"))) + compiler-path (some (fn [^:private path] + (if (.contains path "com/github/luxlang/luxc-jvm") + path + nil)) + jar-paths) + deps-paths (filter (fn [^:private path] + (or (.contains path "org/ow2/asm/asm-all") + (.contains path "org/clojure/core.match") + (.contains path "org/clojure/clojure"))) + jar-paths)] + (let [class-path (->> (classpath/get-classpath project) (filter #(.endsWith % ".jar")) (concat deps-paths) (interpose ":") (reduce str "")) + java-cmd (get project :java-cmd "java") + jvm-opts (->> (get project :jvm-opts) (interpose " ") (reduce str ""))] + (str java-cmd " " jvm-opts " " vm-options " -cp " (str compiler-path ":" class-path) + " lux repl " (->> source-paths (interpose unit-separator) (apply str)))))) + +(defn run-process [command working-directory pre post] + (let [process (.exec (Runtime/getRuntime) command nil working-directory)] + (with-open [std-out (->> process .getInputStream (new InputStreamReader) (new BufferedReader)) + std-err (->> process .getErrorStream (new InputStreamReader) (new BufferedReader))] + (println pre) + (loop [line (.readLine std-out)] + (when line + (println line) + (recur (.readLine std-out)))) + (loop [had-error? false + line (.readLine std-err)] + (if line + (do (println line) + (recur true (.readLine std-err))) + (when had-error? + (System/exit 1)))) + (println post)))) |