├── .gitignore ├── .travis.yml ├── COPYING ├── README.org ├── project.clj ├── res └── templates │ ├── AndroidManifest.library.xml │ ├── AndroidManifest.template.xml │ ├── BuildConfig.java │ ├── LICENSE │ ├── README.library.md │ ├── README.md │ ├── SplashActivity.java │ ├── Util.java │ ├── core.clj │ ├── gitignore │ ├── ic_launcher_hdpi.png │ ├── ic_launcher_mdpi.png │ ├── library.project.clj │ ├── main.clj │ ├── proguard_minify.cfg │ ├── proguard_multi_dex.cfg │ ├── project.clj │ ├── splash_background.xml │ ├── splash_circle.png │ ├── splash_droid.png │ ├── splash_hands.png │ ├── splash_rotation.xml │ ├── splashscreen.xml │ ├── strings.library.xml │ └── strings.xml ├── sample ├── .gitignore ├── AndroidManifest.template.xml ├── README.md ├── build │ ├── proguard-minify.cfg │ └── proguard-multi-dex.cfg ├── project.clj ├── res │ ├── anim │ │ └── splash_rotation.xml │ ├── drawable-hdpi │ │ ├── ic_launcher.png │ │ ├── splash_circle.png │ │ ├── splash_droid.png │ │ └── splash_hands.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable │ │ └── splash_background.xml │ ├── layout │ │ └── splashscreen.xml │ └── values │ │ └── strings.xml └── src │ ├── clojure │ └── test │ │ └── leindroid │ │ └── sample │ │ └── main.clj │ └── java │ └── test │ └── leindroid │ └── sample │ └── SplashActivity.java ├── src ├── lein_droid │ └── plugin.clj └── leiningen │ ├── droid.clj │ └── droid │ ├── aar.clj │ ├── build.clj │ ├── classpath.clj │ ├── code_gen.clj │ ├── compile.clj │ ├── deploy.clj │ ├── manifest.clj │ ├── new.clj │ ├── sdk.clj │ ├── sideload.clj │ ├── test.clj │ └── utils.clj └── travis ├── deploy.sh └── profiles.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /lib 3 | /classes 4 | /checkouts 5 | /docs 6 | pom.xml 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | *~ 13 | .nrepl-port 14 | .lein-repl-history 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | sudo: false 3 | 4 | cache: 5 | directories: 6 | - $HOME/.m2 7 | 8 | before_install: 9 | - mkdir ~/bin 10 | - wget https://raw.github.com/technomancy/leiningen/stable/bin/lein -P ~/bin/ 11 | - chmod a+x ~/bin/lein 12 | - mkdir ~/.lein 13 | - cp travis/profiles.clj ~/.lein/ 14 | 15 | android: 16 | components: 17 | - build-tools-21.1.1 18 | - android-18 19 | - extra-android-m2repository 20 | 21 | lein: ~/bin/lein 22 | 23 | env: 24 | - PROJECT="sample" PREP="cd sample" LEIN_DROID_PROJECT="" DEPLOY="bash travis/deploy.sh" 25 | - PROJECT="newtest" PREP="lein droid new newtest org.clojure_android.newtest" LEIN_DROID_PROJECT="newtest/project.clj" DEPLOY="" 26 | 27 | script: 28 | - $PREP 29 | - DEBUG=1 LEIN_DROID_PROFILES=travis,dev lein with-profile travis,dev do droid build, droid apk 30 | - DEBUG=1 LEIN_DROID_PROFILES=travis,release lein with-profile travis,release do droid build, droid apk 31 | - DEBUG=1 LEIN_DROID_PROFILES=travis,lean lein with-profile travis,lean do clean, droid build, droid apk 32 | 33 | after_success: 34 | - cd .. 35 | - $DEPLOY 36 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Distributed under the Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF 5 | THE PROGRAM CONSTITUTES RECIPIENT’S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and 12 | documentation distributed under this Agreement, and 13 | b) in the case of each subsequent Contributor: 14 | 15 | i)changes to the Program, and 16 | 17 | ii)additions to the Program; 18 | 19 | where such changes and/or additions to the Program originate from and 20 | are distributed by that particular Contributor. A Contribution 21 | 'originates' from a Contributor if it was added to the Program by such 22 | Contributor itself or anyone acting on such Contributor’s behalf. 23 | Contributions do not include additions to the Program which: (i) are 24 | separate modules of software distributed in conjunction with the 25 | Program under their own license agreement, and (ii) are not derivative 26 | works of the Program. 27 | 28 | "Contributor" means any person or entity that distributes the Program. 29 | 30 | "Licensed Patents " mean patent claims licensable by a Contributor 31 | which are necessarily infringed by the use or sale of its Contribution 32 | alone or when combined with the Program. 33 | 34 | "Program" means the Contributions distributed in accordance with this 35 | Agreement. 36 | 37 | "Recipient" means anyone who receives the Program under this 38 | Agreement, including all Contributors. 39 | 40 | 2. GRANT OF RIGHTS 41 | 42 | a) Subject to the terms of this Agreement, each Contributor hereby 43 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 44 | license to reproduce, prepare derivative works of, publicly display, 45 | publicly perform, distribute and sublicense the Contribution of such 46 | Contributor, if any, and such derivative works, in source code and 47 | object code form. 48 | 49 | b) Subject to the terms of this Agreement, each Contributor hereby 50 | grants Recipient a non-exclusive, worldwide, royalty-free patent 51 | license under Licensed Patents to make, use, sell, offer to sell, 52 | import and otherwise transfer the Contribution of such Contributor, if 53 | any, in source code and object code form. This patent license shall 54 | apply to the combination of the Contribution and the Program if, at 55 | the time the Contribution is added by the Contributor, such addition 56 | of the Contribution causes such combination to be covered by the 57 | Licensed Patents. The patent license shall not apply to any other 58 | combinations which include the Contribution. No hardware per se is 59 | licensed hereunder. 60 | 61 | c) Recipient understands that although each Contributor grants the 62 | licenses to its Contributions set forth herein, no assurances are 63 | provided by any Contributor that the Program does not infringe the 64 | patent or other intellectual property rights of any other entity. Each 65 | Contributor disclaims any liability to Recipient for claims brought by 66 | any other entity based on infringement of intellectual property rights 67 | or otherwise. As a condition to exercising the rights and licenses 68 | granted hereunder, each Recipient hereby assumes sole responsibility 69 | to secure any other intellectual property rights needed, if any. For 70 | example, if a third party patent license is required to allow 71 | Recipient to distribute the Program, it is Recipient’s responsibility 72 | to acquire that license before distributing the Program. 73 | 74 | d) Each Contributor represents that to its knowledge it has sufficient 75 | copyright rights in its Contribution, if any, to grant the copyright 76 | license set forth in this Agreement. 77 | 78 | 3. REQUIREMENTS 79 | 80 | A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: 81 | 82 | a) it complies with the terms and conditions of this Agreement; and 83 | 84 | b) its license agreement: 85 | 86 | i) effectively disclaims on behalf of all Contributors all 87 | warranties and conditions, express and implied, including 88 | warranties or conditions of title and non-infringement, and 89 | implied warranties or conditions of merchantability and fitness 90 | for a particular purpose; 91 | 92 | ii) effectively excludes on behalf of all Contributors all 93 | liability for damages, including direct, indirect, special, 94 | incidental and consequential damages, such as lost profits; 95 | 96 | iii) states that any provisions which differ from this Agreement 97 | are offered by that Contributor alone and not by any other party; 98 | and 99 | 100 | iv) states that source code for the Program is available from such 101 | Contributor, and informs licensees how to obtain it in a 102 | reasonable manner on or through a medium customarily used for 103 | software exchange. 104 | 105 | When the Program is made available in source code form: 106 | 107 | a) it must be made available under this Agreement; and 108 | 109 | b) a copy of this Agreement must be included with each copy of the 110 | Program. 111 | 112 | Contributors may not remove or alter any copyright notices contained 113 | within the Program. 114 | 115 | Each Contributor must identify itself as the originator of its 116 | Contribution, if any, in a manner that reasonably allows subsequent 117 | Recipients to identify the originator of the Contribution. 118 | 119 | 4. COMMERCIAL DISTRIBUTION 120 | 121 | Commercial distributors of software may accept certain 122 | responsibilities with respect to end users, business partners and the 123 | like. While this license is intended to facilitate the commercial use 124 | of the Program, the Contributor who includes the Program in a 125 | commercial product offering should do so in a manner which does not 126 | create potential liability for other Contributors. Therefore, if a 127 | Contributor includes the Program in a commercial product offering, 128 | such Contributor ("Commercial Contributor") hereby agrees to defend 129 | and indemnify every other Contributor ("Indemnified Contributor") 130 | against any losses, damages and costs (collectively "Losses") arising 131 | from claims, lawsuits and other legal actions brought by a third party 132 | against the Indemnified Contributor to the extent caused by the acts 133 | or omissions of such Commercial Contributor in connection with its 134 | distribution of the Program in a commercial product offering. The 135 | obligations in this section do not apply to any claims or Losses 136 | relating to any actual or alleged intellectual property infringement. 137 | In order to qualify, an Indemnified Contributor must: a) promptly 138 | notify the Commercial Contributor in writing of such claim, and b) 139 | allow the Commercial Contributor to control, and cooperate with the 140 | Commercial Contributor in, the defense and any related settlement 141 | negotiations. The Indemnified Contributor may participate in any such 142 | claim at its own expense. 143 | 144 | For example, a Contributor might include the Program in a commercial 145 | product offering, Product X. That Contributor is then a Commercial 146 | Contributor. If that Commercial Contributor then makes performance 147 | claims, or offers warranties related to Product X, those performance 148 | claims and warranties are such Commercial Contributor’s responsibility 149 | alone. Under this section, the Commercial Contributor would have to 150 | defend claims against the other Contributors related to those 151 | performance claims and warranties, and if a court requires any other 152 | Contributor to pay any damages as a result, the Commercial Contributor 153 | must pay those damages. 154 | 155 | 5. NO WARRANTY 156 | 157 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 158 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 159 | KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY 160 | WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 161 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 162 | responsible for determining the appropriateness of using and 163 | distributing the Program and assumes all risks associated with its 164 | exercise of rights under this Agreement , including but not limited to 165 | the risks and costs of program errors, compliance with applicable 166 | laws, damage to or loss of data, programs or equipment, and 167 | unavailability or interruption of operations. 168 | 169 | 6. DISCLAIMER OF LIABILITY 170 | 171 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR 172 | ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 173 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 174 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 175 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 176 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 177 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 178 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 179 | 180 | 7. GENERAL 181 | 182 | If any provision of this Agreement is invalid or unenforceable under 183 | applicable law, it shall not affect the validity or enforceability of 184 | the remainder of the terms of this Agreement, and without further 185 | action by the parties hereto, such provision shall be reformed to the 186 | minimum extent necessary to make such provision valid and enforceable. 187 | 188 | If Recipient institutes patent litigation against any entity 189 | (including a cross-claim or counterclaim in a lawsuit) alleging that 190 | the Program itself (excluding combinations of the Program with other 191 | software or hardware) infringes such Recipient’s patent(s), then such 192 | Recipient’s rights granted under Section 2(b) shall terminate as of 193 | the date such litigation is filed. 194 | 195 | All Recipient’s rights under this Agreement shall terminate if it 196 | fails to comply with any of the material terms or conditions of this 197 | Agreement and does not cure such failure in a reasonable period of 198 | time after becoming aware of such noncompliance. If all Recipient’s 199 | rights under this Agreement terminate, Recipient agrees to cease use 200 | and distribution of the Program as soon as reasonably practicable. 201 | However, Recipient’s obligations under this Agreement and any licenses 202 | granted by Recipient relating to the Program shall continue and 203 | survive. 204 | 205 | Everyone is permitted to copy and distribute copies of this Agreement, 206 | but in order to avoid inconsistency the Agreement is copyrighted and 207 | may only be modified in the following manner. The Agreement Steward 208 | reserves the right to publish new versions (including revisions) of 209 | this Agreement from time to time. No one other than the Agreement 210 | Steward has the right to modify this Agreement. The Eclipse Foundation 211 | is the initial Agreement Steward. The Eclipse Foundation may assign 212 | the responsibility to serve as the Agreement Steward to a suitable 213 | separate entity. Each new version of the Agreement will be given a 214 | distinguishing version number. The Program (including Contributions) 215 | may always be distributed subject to the version of the Agreement 216 | under which it was received. In addition, after a new version of the 217 | Agreement is published, Contributor may elect to distribute the 218 | Program (including its Contributions) under the new version. Except as 219 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives 220 | no rights or licenses to the intellectual property of any Contributor 221 | under this Agreement, whether expressly, by implication, estoppel or 222 | otherwise. All rights in the Program not expressly granted under this 223 | Agreement are reserved. 224 | 225 | This Agreement is governed by the laws of the State of New York and 226 | the intellectual property laws of the United States of America. No 227 | party to this Agreement will bring a legal action under this Agreement 228 | more than one year after the cause of action arose. Each party waives 229 | its rights to a jury trial in any resulting litigation. 230 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * lein-droid 2 | 3 | [[https://travis-ci.org/clojure-android/lein-droid/][https://travis-ci.org/clojure-android/lein-droid.svg?branch=master]] 4 | 5 | A Leiningen plugin to simplify Clojure development for Android 6 | platform. It acts as a build-tool for Clojure/Android projects. 7 | 8 | ** Usage 9 | 10 | First make sure you have [[http://developer.android.com/sdk/index.html][Android SDK]] installed, with the latest 11 | Android SDK Build-tools and Android Support Repository. 12 | 13 | Follow the [[https://github.com/clojure-android/lein-droid/wiki/Tutorial][Tutorial]] to start using lein-droid. 14 | 15 | Latest release: 16 | 17 | [[https://clojars.org/lein-droid][https://clojars.org/lein-droid/latest-version.svg]] 18 | 19 | ** Documentation 20 | 21 | The [[https://github.com/clojure-android/lein-droid/wiki][wiki]] is a primary source of lein-droid documentation. To know more about 22 | =project.clj= options lein-droid supports, consult [[https://github.com/clojure-android/lein-droid/wiki/project.clj-options][this page]]. 23 | 24 | If you'd like to read the code, Marginalia docs are [[http://clojure-android.github.io/lein-droid/][here]]. 25 | 26 | [[https://github.com/krisc][Kris Calabio]] wrote a [[https://github.com/alexander-yakushev/events/blob/master/tutorial.md][tutorial]] on how to write your first real Clojure/Android 27 | application. 28 | 29 | ** How to modify lein-droid 30 | 31 | Being a plugin for Leiningen, the workflow for modifying, debugging and 32 | testing it is slightly more complicated than with a regular Clojure project. 33 | See [[https://github.com/clojure-android/lein-droid/wiki/Hacking-the-plugin][Hacking the plugin]] page for details. 34 | 35 | ** Contributors 36 | 37 | I thank the following people for their help in extending and 38 | improving lein-droid: 39 | 40 | - [[https://github.com/AdamClements][Adam Clements]] 41 | - [[https://github.com/oakes][Zach Oakes]] 42 | - [[https://github.com/ayamada][Atsuo Yamada]] 43 | - [[https://github.com/sergv][Sergey Vinokurov]] 44 | - [[https://github.com/clojure-android/lein-droid/graphs/contributors][and others]] 45 | 46 | ** License 47 | 48 | Copyright © 2012-2015 Alexander Yakushev. Distributed under the Eclipse 49 | Public License, the same as Clojure. See the file [[https://github.com/clojure-android/lein-droid/blob/master/COPYING][COPYING]]. 50 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject lein-droid/lein-droid "0.4.6" 2 | :description "Plugin for easy Clojure/Android development and deployment" 3 | :url "https://github.com/clojure-android/lein-droid" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[robert/hooke "1.3.0"] 7 | [org.clojure/data.zip "0.1.1"] 8 | [net.lingala.zip4j/zip4j "1.3.2"] 9 | [com.android.tools.build/manifest-merger "24.2.3"] 10 | [de.ubercode.clostache/clostache "1.4.0"]] 11 | :resource-paths ["res"] 12 | :eval-in-leiningen true) 13 | -------------------------------------------------------------------------------- /res/templates/AndroidManifest.library.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /res/templates/AndroidManifest.template.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | {[={{ }}=]} 8 | 9 | {{={[ ]}=}} 10 | 12 | {[={{ }}=]} 13 | 14 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | {{={[ ]}=}} 34 | {{#debug-build}} 35 | 36 | 37 | 38 | 39 | {{/debug-build}} 40 | {[={{ }}=]} 41 | 42 | -------------------------------------------------------------------------------- /res/templates/BuildConfig.java: -------------------------------------------------------------------------------- 1 | package {{{package-name}}}; 2 | 3 | /* This file is autogenerated from the values in :android 4 | * {:build-config {"NAME" value}}*/ 5 | 6 | public class BuildConfig { 7 | public static final boolean DEBUG = {{{debug}}}; 8 | {{#constants}} 9 | public static final {{{type}}} {{{key}}} = {{{value}}}; 10 | {{/constants}} 11 | } 12 | -------------------------------------------------------------------------------- /res/templates/LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of Washington and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /res/templates/README.library.md: -------------------------------------------------------------------------------- 1 | # {{app-name}} 2 | 3 | This is a Clojure/Android library. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2015 FIXME 12 | 13 | Distributed under the Eclipse Public License, the same as Clojure. 14 | -------------------------------------------------------------------------------- /res/templates/README.md: -------------------------------------------------------------------------------- 1 | # {{app-name}} 2 | 3 | This is a Clojure/Android application. 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2015 FIXME 12 | 13 | Distributed under the Eclipse Public License, the same as Clojure. 14 | -------------------------------------------------------------------------------- /res/templates/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package {{package}}; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.animation.Animation; 7 | import android.view.animation.AnimationUtils; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | import neko.App; 11 | 12 | import {{package}}.R; 13 | 14 | public class SplashActivity extends Activity { 15 | 16 | private static boolean firstLaunch = true; 17 | 18 | @Override 19 | public void onCreate(Bundle bundle) { 20 | super.onCreate(bundle); 21 | 22 | if (firstLaunch) { 23 | firstLaunch = false; 24 | setupSplash(); 25 | App.loadAsynchronously("{{package}}.{{activity}}", 26 | new Runnable() { 27 | @Override 28 | public void run() { 29 | proceed(); 30 | }}); 31 | } else { 32 | proceed(); 33 | } 34 | } 35 | 36 | public void setupSplash() { 37 | setContentView(R.layout.splashscreen); 38 | 39 | TextView appNameView = (TextView)findViewById(R.id.splash_app_name); 40 | appNameView.setText(R.string.app_name); 41 | 42 | Animation rotation = AnimationUtils.loadAnimation(this, R.anim.splash_rotation); 43 | ImageView circleView = (ImageView)findViewById(R.id.splash_circles); 44 | circleView.startAnimation(rotation); 45 | } 46 | 47 | public void proceed() { 48 | startActivity(new Intent("{{package-sanitized}}.MAIN")); 49 | finish(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /res/templates/Util.java: -------------------------------------------------------------------------------- 1 | package {{package}}; 2 | 3 | import android.content.Context; 4 | 5 | import {{package}}.R; 6 | 7 | public class Util { 8 | 9 | public static String getName(Context c) { 10 | return c.getString(R.string.{{name}}_library_name); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /res/templates/core.clj: -------------------------------------------------------------------------------- 1 | (ns {{package}}.core) 2 | 3 | (defn sum [a b] 4 | (+ a b)) 5 | -------------------------------------------------------------------------------- /res/templates/gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /gen 4 | /checkouts 5 | pom.xml 6 | pom.xml.asc 7 | *.jar 8 | *.class 9 | /.lein-* 10 | /.nrepl-port 11 | -------------------------------------------------------------------------------- /res/templates/ic_launcher_hdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-android/lein-droid/1e0f8d5a5dd3c53232e21a1874867c212edb9848/res/templates/ic_launcher_hdpi.png -------------------------------------------------------------------------------- /res/templates/ic_launcher_mdpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-android/lein-droid/1e0f8d5a5dd3c53232e21a1874867c212edb9848/res/templates/ic_launcher_mdpi.png -------------------------------------------------------------------------------- /res/templates/library.project.clj: -------------------------------------------------------------------------------- 1 | (defproject {{name}}/{{name}} "0.1.0-SNAPSHOT" 2 | :description "FIXME: Android library description" 3 | :packaging "aar" 4 | 5 | :javac-options ["-target" "1.6" "-source" "1.6" "-Xlint:-options"] 6 | {{#new-project}} 7 | :source-paths ["src/clojure"] 8 | :java-source-paths ["src/java"] 9 | {{/new-project}} 10 | {{^new-project}} 11 | :java-source-paths ["src"] 12 | 13 | ;; Uncomment this line if your project doesn't use Clojure. 14 | ;; :java-only true 15 | {{/new-project}} 16 | 17 | :plugins [[lein-droid "{{lein-droid-version}}"]] 18 | :profiles {:default [:android-common]} 19 | 20 | :android {:target-version "{{target-sdk}}" 21 | :library true 22 | {{^new-project}} 23 | :manifest-template-path "AndroidManifest.xml" 24 | {{/new-project}} 25 | }) 26 | -------------------------------------------------------------------------------- /res/templates/main.clj: -------------------------------------------------------------------------------- 1 | (ns {{package}}.main 2 | (:require [neko.activity :refer [defactivity set-content-view!]] 3 | [neko.debug :refer [*a]] 4 | [neko.notify :refer [toast]] 5 | [neko.resource :as res] 6 | [neko.find-view :refer [find-view]] 7 | [neko.threading :refer [on-ui]]) 8 | (:import android.widget.EditText)) 9 | 10 | ;; We execute this function to import all subclasses of R class. This gives us 11 | ;; access to all application resources. 12 | (res/import-all) 13 | 14 | (defn notify-from-edit 15 | "Finds an EditText element with ID ::user-input in the given activity. Gets 16 | its contents and displays them in a toast if they aren't empty. We use 17 | resources declared in res/values/strings.xml." 18 | [activity] 19 | (let [^EditText input (.getText (find-view activity ::user-input))] 20 | (toast (if (empty? input) 21 | (res/get-string R$string/input_is_empty) 22 | (res/get-string R$string/your_input_fmt input)) 23 | :long))) 24 | 25 | ;; This is how an Activity is defined. We create one and specify its onCreate 26 | ;; method. Inside we create a user interface that consists of an edit and a 27 | ;; button. We also give set callback to the button. 28 | (defactivity {{package}}.{{activity}} 29 | :key :main 30 | 31 | (onCreate [this bundle] 32 | (.superOnCreate this bundle) 33 | (neko.debug/keep-screen-on this) 34 | (on-ui 35 | (set-content-view! (*a) 36 | [:linear-layout {:orientation :vertical 37 | :layout-width :fill 38 | :layout-height :wrap} 39 | [:edit-text {:id ::user-input 40 | :hint "Type text here" 41 | :layout-width :fill}] 42 | [:button {:text R$string/touch_me ;; We use resource here, but could 43 | ;; have used a plain string too. 44 | :on-click (fn [_] (notify-from-edit (*a)))}]])))) 45 | -------------------------------------------------------------------------------- /res/templates/proguard_minify.cfg: -------------------------------------------------------------------------------- 1 | # This is a configuration file for ProGuard. 2 | # http://proguard.sourceforge.net/index.html#manual/usage.html 3 | 4 | -libraryjars /lib/rt.jar 5 | 6 | -dontusemixedcaseclassnames 7 | -dontskipnonpubliclibraryclasses 8 | -verbose 9 | 10 | # Optimization is turned off by default. Dex does not like code run 11 | # through the ProGuard optimize and preverify steps (and performs some 12 | # of these optimizations on its own). 13 | # -optimizations code/removal/advanced 14 | # !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* 15 | # -optimizationpasses 2 16 | -dontoptimize 17 | -dontpreverify 18 | -dontobfuscate 19 | -dontnote 20 | 21 | # Note that if you want to enable optimization, you cannot just include 22 | # optimization flags in your own project configuration file; instead use 23 | # "proguard-android-optimize.txt" file instead from your SDK folder. 24 | 25 | -keepattributes *Annotation* 26 | -keep public class com.google.vending.licensing.ILicensingService 27 | -keep public class com.android.vending.licensing.ILicensingService 28 | 29 | # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native 30 | -keepclasseswithmembernames class * { 31 | native ; 32 | } 33 | 34 | # keep setters in Views so that animations can still work. 35 | # see http://proguard.sourceforge.net/manual/examples.html#beans 36 | -keepclassmembers public class * extends android.view.View { 37 | void set*(***); 38 | *** get*(); 39 | } 40 | 41 | # We want to keep methods in Activity that could be used in the XML attribute onClick 42 | -keepclassmembers class * extends android.app.Activity { 43 | public void *(android.view.View); 44 | public void super*(...); 45 | } 46 | 47 | # For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations 48 | -keepclassmembers enum * { 49 | public static **[] values(); 50 | public static ** valueOf(java.lang.String); 51 | } 52 | 53 | -keepclassmembers class **.R$* { 54 | public static ; 55 | } 56 | 57 | -keep public class clojure.lang.Fn 58 | -keep public class neko.App 59 | -keep public class **__init 60 | 61 | -keep public class {{package-sanitized}}.* 62 | 63 | # The support library contains references to newer platform versions. 64 | # Don't warn about those in case this app is linking against an older 65 | # platform version. We know about them, and they are safe. 66 | # -dontwarn android.support.** 67 | -------------------------------------------------------------------------------- /res/templates/proguard_multi_dex.cfg: -------------------------------------------------------------------------------- 1 | -dontoptimize 2 | -dontpreverify 3 | -dontobfuscate 4 | -dontwarn 5 | -dontnote 6 | -forceprocessing 7 | 8 | -keep class clojure.** 9 | 10 | -keep public class * extends android.app.Instrumentation { 11 | (); 12 | } 13 | -keep public class * extends android.app.Application { 14 | (); 15 | void attachBaseContext(android.content.Context); 16 | } 17 | -keep public class * extends android.app.Activity { 18 | (); 19 | } 20 | -keep public class * extends android.app.Service { 21 | (); 22 | } 23 | -keep public class * extends android.content.ContentProvider { 24 | (); 25 | } 26 | -keep public class * extends android.content.BroadcastReceiver { 27 | (); 28 | } 29 | -keep public class * extends android.app.backup.BackupAgent { 30 | (); 31 | } 32 | # We need to keep all annotation classes because proguard does not trace annotation attribute 33 | # it just filter the annotation attributes according to annotation classes it already kept. 34 | -keep public class * extends java.lang.annotation.Annotation { 35 | *; 36 | } 37 | -------------------------------------------------------------------------------- /res/templates/project.clj: -------------------------------------------------------------------------------- 1 | (defproject {{name}}/{{name}} "0.1.0-SNAPSHOT" 2 | :description "FIXME: Android project description" 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :global-vars {clojure.core/*warn-on-reflection* true} 8 | 9 | :source-paths ["src/clojure" "src"] 10 | :java-source-paths ["src/java"] 11 | :javac-options ["-target" "1.6" "-source" "1.6" "-Xlint:-options"] 12 | :plugins [[lein-droid "{{lein-droid-version}}"]] 13 | 14 | :dependencies [[org.clojure-android/clojure "{{clojure-version}}"] 15 | [neko/neko "{{neko-version}}"]] 16 | :profiles {:default [:dev] 17 | 18 | :dev 19 | [:android-common :android-user 20 | {:dependencies [[org.clojure/tools.nrepl "0.2.10"]] 21 | :target-path "target/debug" 22 | :android {:aot :all-with-unused 23 | :manifest-options {:app-name "{{app-name}} (debug)"} 24 | ;; Uncomment to be able install debug and release side-by-side. 25 | ;; :rename-manifest-package "{{package-sanitized}}.debug" 26 | }}] 27 | :release 28 | [:android-common 29 | {:target-path "target/release" 30 | :android 31 | {;; :keystore-path "/home/user/.android/private.keystore" 32 | ;; :key-alias "mykeyalias" 33 | ;; :sigalg "MD5withRSA" 34 | 35 | :use-debug-keystore true 36 | :ignore-log-priority [:debug :verbose] 37 | :aot :all 38 | :build-type :release}}] 39 | 40 | :lean 41 | [:release 42 | {:dependencies ^:replace [[org.skummet/clojure "{{skummet-version}}"] 43 | [neko/neko "{{neko-version}}"]] 44 | :exclusions [[org.clojure/clojure] 45 | [org.clojure-android/clojure]] 46 | :jvm-opts ["-Dclojure.compile.ignore-lean-classes=true"] 47 | :android {:lean-compile true 48 | :proguard-execute true 49 | :proguard-conf-path "build/proguard-minify.cfg"}}]} 50 | 51 | :android {;; Specify the path to the Android SDK directory. 52 | ;; :sdk-path "/home/user/path/to/android-sdk/" 53 | 54 | ;; Increase this value if dexer fails with OutOfMemoryException. 55 | :dex-opts ["-JXmx4096M" "--incremental"] 56 | 57 | :target-version "{{target-sdk}}" 58 | :aot-exclude-ns ["clojure.parallel" "clojure.core.reducers" 59 | "cider.nrepl" "cider-nrepl.plugin" 60 | "cider.nrepl.middleware.util.java.parser" 61 | #"cljs-tooling\..+"]}) 62 | -------------------------------------------------------------------------------- /res/templates/splash_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /res/templates/splash_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-android/lein-droid/1e0f8d5a5dd3c53232e21a1874867c212edb9848/res/templates/splash_circle.png -------------------------------------------------------------------------------- /res/templates/splash_droid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-android/lein-droid/1e0f8d5a5dd3c53232e21a1874867c212edb9848/res/templates/splash_droid.png -------------------------------------------------------------------------------- /res/templates/splash_hands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-android/lein-droid/1e0f8d5a5dd3c53232e21a1874867c212edb9848/res/templates/splash_hands.png -------------------------------------------------------------------------------- /res/templates/splash_rotation.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /res/templates/splashscreen.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 14 | 19 | 20 | 25 | 26 | 27 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | 50 | 51 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /res/templates/strings.library.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{app-name}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /res/templates/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{app-name}} 5 | Your input is empty 6 | Your input: %1$s 7 | Touch me 8 | 9 | 10 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /gen 3 | /lib 4 | /classes 5 | /checkouts 6 | pom.xml 7 | *.jar 8 | *.class 9 | .lein-deps-sum 10 | .lein-failures 11 | .lein-plugins 12 | .nrepl-port 13 | -------------------------------------------------------------------------------- /sample/AndroidManifest.template.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {{#debug-build}} 29 | 30 | 31 | 32 | 33 | {{/debug-build}} 34 | 35 | -------------------------------------------------------------------------------- /sample/README.md: -------------------------------------------------------------------------------- 1 | # sample 2 | 3 | This is an application for testing the lein-droid plugin for Leiningen 4 | 5 | ## Usage 6 | 7 | FIXME 8 | 9 | ## License 10 | 11 | Copyright © 2012 FIXME 12 | 13 | Distributed under the Eclipse Public License, the same as Clojure. 14 | -------------------------------------------------------------------------------- /sample/build/proguard-minify.cfg: -------------------------------------------------------------------------------- 1 | # This is a configuration file for ProGuard. 2 | # http://proguard.sourceforge.net/index.html#manual/usage.html 3 | 4 | -libraryjars /lib/rt.jar 5 | 6 | -dontusemixedcaseclassnames 7 | -dontskipnonpubliclibraryclasses 8 | -verbose 9 | 10 | # Optimization is turned off by default. Dex does not like code run 11 | # through the ProGuard optimize and preverify steps (and performs some 12 | # of these optimizations on its own). 13 | # -optimizations code/removal/advanced 14 | # !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* 15 | # -optimizationpasses 2 16 | -dontoptimize 17 | -dontpreverify 18 | -dontobfuscate 19 | -dontnote 20 | 21 | # Note that if you want to enable optimization, you cannot just include 22 | # optimization flags in your own project configuration file; instead use 23 | # "proguard-android-optimize.txt" file instead from your SDK folder. 24 | 25 | -keepattributes *Annotation* 26 | -keep public class com.google.vending.licensing.ILicensingService 27 | -keep public class com.android.vending.licensing.ILicensingService 28 | 29 | # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native 30 | -keepclasseswithmembernames class * { 31 | native ; 32 | } 33 | 34 | # keep setters in Views so that animations can still work. 35 | # see http://proguard.sourceforge.net/manual/examples.html#beans 36 | -keepclassmembers public class * extends android.view.View { 37 | void set*(***); 38 | *** get*(); 39 | } 40 | 41 | # We want to keep methods in Activity that could be used in the XML attribute onClick 42 | -keepclassmembers class * extends android.app.Activity { 43 | public void *(android.view.View); 44 | public void super*(...); 45 | } 46 | 47 | # For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations 48 | -keepclassmembers enum * { 49 | public static **[] values(); 50 | public static ** valueOf(java.lang.String); 51 | } 52 | 53 | -keepclassmembers class **.R$* { 54 | public static ; 55 | } 56 | 57 | -keep public class clojure.lang.Fn 58 | -keep public class neko.App 59 | -keep public class **__init 60 | 61 | -keep public class test.leindroid.sample.* 62 | 63 | # The support library contains references to newer platform versions. 64 | # Don't warn about those in case this app is linking against an older 65 | # platform version. We know about them, and they are safe. 66 | # -dontwarn android.support.** 67 | 68 | # Uncomment if you want to know why specific class is kept 69 | -whyareyoukeeping class clojure.core$subvec# neko.tools.repl$patch_unsupported_dependencies 70 | 71 | -------------------------------------------------------------------------------- /sample/build/proguard-multi-dex.cfg: -------------------------------------------------------------------------------- 1 | -dontoptimize 2 | -dontpreverify 3 | -dontobfuscate 4 | -dontwarn 5 | -dontnote 6 | -forceprocessing 7 | 8 | -keep class clojure.** 9 | 10 | -keep public class * extends android.app.Instrumentation { 11 | (); 12 | } 13 | -keep public class * extends android.app.Application { 14 | (); 15 | void attachBaseContext(android.content.Context); 16 | } 17 | -keep public class * extends android.app.Activity { 18 | (); 19 | } 20 | -keep public class * extends android.app.Service { 21 | (); 22 | } 23 | -keep public class * extends android.content.ContentProvider { 24 | (); 25 | } 26 | -keep public class * extends android.content.BroadcastReceiver { 27 | (); 28 | } 29 | -keep public class * extends android.app.backup.BackupAgent { 30 | (); 31 | } 32 | # We need to keep all annotation classes because proguard does not trace annotation attribute 33 | # it just filter the annotation attributes according to annotation classes it already kept. 34 | -keep public class * extends java.lang.annotation.Annotation { 35 | *; 36 | } 37 | -------------------------------------------------------------------------------- /sample/project.clj: -------------------------------------------------------------------------------- 1 | (defproject sample/sample "0.0.1-SNAPSHOT" 2 | :description "Sample Android project to test lein-droid plugin." 3 | :url "http://example.com/FIXME" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | 7 | :global-vars {*warn-on-reflection* true} 8 | 9 | :source-paths ["src/clojure" "src"] 10 | :java-source-paths ["src/java"] 11 | :javac-options ["-target" "1.6" "-source" "1.6" "-Xlint:-options"] 12 | 13 | :plugins [[lein-droid "0.4.6"]] 14 | 15 | :dependencies [[org.clojure-android/clojure "1.7.0-RC1" :use-resources true] 16 | ;; [com.google.android.gms/play-services "7.0.0" :extension "aar"] 17 | ;; [com.android.support/appcompat-v7 "18.0.0" :extension "aar"] 18 | [neko/neko "4.0.0-alpha5"]] 19 | 20 | :profiles {:default [:dev] 21 | 22 | :dev 23 | [:android-common :android-user 24 | ;; The above profiles can be specified in your profiles.clj and 25 | ;; contain machine-specific options such as {:android {:sdk-path 26 | ;; "/path/to/sdk"}}. :android-user profile is for global 27 | ;; dev-related options like CIDER configuration. 28 | 29 | {:dependencies [[org.clojure/tools.nrepl "0.2.10"]] 30 | :target-path "target/debug" 31 | :android {:aot :all-with-unused 32 | ;; The namespace of the app package - having a 33 | ;; different one for dev and release allows you to 34 | ;; install both at the same time. 35 | :rename-manifest-package "test.leindroid.sample.debug" 36 | :manifest-options {:app-name "Android-Clojure (debug)"} 37 | }}] 38 | 39 | :release 40 | [:android-common 41 | {:target-path "target/release" 42 | :android { ;; Specify the path to your private keystore and the 43 | ;; the alias of the key you want to sign APKs with. 44 | ;; :keystore-path "/home/user/.android/private.keystore" 45 | ;; :key-alias "mykeyalias" 46 | ;; :sigalg "MD5withRSA" 47 | 48 | ;; You can specify these to avoid entering them for 49 | ;; each rebuild, but generally it's a bad idea. 50 | ;; :keypass "android" 51 | ;; :storepass "android" 52 | 53 | :ignore-log-priority [:debug :verbose] 54 | :aot :all 55 | 56 | ;; This tells lein-droid to build in release mode, 57 | ;; disabling debugging and signing the resulting 58 | ;; package. 59 | :build-type :release}}] 60 | 61 | :lean 62 | [:release 63 | {:dependencies ^:replace [[org.skummet/clojure "1.7.0-r1"] 64 | [neko/neko "4.0.0-alpha5"]] 65 | :exclusions [[org.clojure/clojure] 66 | [org.clojure-android/clojure]] 67 | :jvm-opts ["-Dclojure.compile.ignore-lean-classes=true"] 68 | :global-vars ^:replace {clojure.core/*warn-on-reflection* true} 69 | :android {:use-debug-keystore true 70 | :proguard-execute true 71 | :proguard-conf-path "build/proguard-minify.cfg" 72 | :lean-compile true 73 | :skummet-skip-vars [;; You can list here var names that 74 | ;; you want to keep non-lean. E.g.: 75 | ;; "#'foo.bar/my-function" 76 | ]}}] 77 | 78 | ;; Here's an example of using different profiles 79 | :trial-version-dev 80 | [:dev ; Inherits from :dev profile 81 | {:android {:rename-manifest-package "my.sample.app.dev.trial" 82 | ;; And then some options which might be 83 | ;; additional/different source-paths to pull in different 84 | ;; code, or a manifest option which configures some aspect 85 | ;; of your application. 86 | }}]} 87 | 88 | :android {;; Specify the path to the Android SDK directory either here or in 89 | ;; :android-common profile in your ~/.lein/profiles.clj file. 90 | ;; :sdk-path "/home/user/path/to/android-sdk/" 91 | 92 | ;; Use this if you don't want to use the latest version of 93 | ;; Android Build Tools. 94 | ;; :build-tools-version "19.0.3" 95 | 96 | ;; Specify this if your project is a library. 97 | ;; :library true 98 | 99 | :dex-opts ["-JXmx4096M" "--incremental"] 100 | 101 | ;; If you hit the 65k method limit while dexing, uncomment the 102 | ;; following lines. Also remove the --incremental option above. 103 | ;; :multi-dex true 104 | ;; :multi-dex-proguard-conf-path "build/proguard-multi-dex.cfg" 105 | 106 | ;; Sequence of external jars or class folders to include 107 | ;; into project. 108 | ;; :external-classes-paths ["path/to/external/jar/file" 109 | ;; "path/to/classfiles/"] 110 | 111 | ;; Sequence of jars, resources from which will be added to 112 | ;; application package. 113 | ;; :resource-jars-paths ["path/to/resource/jar"] 114 | 115 | ;; Sequence of native libraries files that will be added 116 | ;; to application package. 117 | ;; :native-libraries-paths ["path/to/native/library"] 118 | 119 | ;; Target version affects api used for compilation. 120 | :target-version 18 121 | 122 | ;; Sequence of namespaces that should not be compiled. 123 | :aot-exclude-ns ["clojure.parallel" "clojure.core.reducers"] 124 | 125 | ;; This specifies replacements which are inserted into 126 | ;; AndroidManifest-template.xml at build time. See Clostache for 127 | ;; more advanced substitution syntax. Version name and code are 128 | ;; automatically inserted 129 | :manifest-options {:app-name "@string/app_name"} 130 | }) 131 | -------------------------------------------------------------------------------- /sample/res/anim/splash_rotation.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /sample/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-android/lein-droid/1e0f8d5a5dd3c53232e21a1874867c212edb9848/sample/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/res/drawable-hdpi/splash_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-android/lein-droid/1e0f8d5a5dd3c53232e21a1874867c212edb9848/sample/res/drawable-hdpi/splash_circle.png -------------------------------------------------------------------------------- /sample/res/drawable-hdpi/splash_droid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-android/lein-droid/1e0f8d5a5dd3c53232e21a1874867c212edb9848/sample/res/drawable-hdpi/splash_droid.png -------------------------------------------------------------------------------- /sample/res/drawable-hdpi/splash_hands.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-android/lein-droid/1e0f8d5a5dd3c53232e21a1874867c212edb9848/sample/res/drawable-hdpi/splash_hands.png -------------------------------------------------------------------------------- /sample/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clojure-android/lein-droid/1e0f8d5a5dd3c53232e21a1874867c212edb9848/sample/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/res/drawable/splash_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /sample/res/layout/splashscreen.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 14 | 19 | 20 | 25 | 26 | 27 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | 50 | 51 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /sample/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Android-Clojure 5 | Your input is empty 6 | Your input: %1$s 7 | Touch me 8 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/clojure/test/leindroid/sample/main.clj: -------------------------------------------------------------------------------- 1 | (ns test.leindroid.sample.main 2 | (:require [neko.activity :refer [defactivity set-content-view!]] 3 | [neko.debug :refer [*a]] 4 | [neko.notify :refer [toast]] 5 | [neko.resource :as res] 6 | [neko.find-view :refer [find-view]] 7 | [neko.threading :refer [on-ui]]) 8 | (:import android.widget.EditText)) 9 | 10 | ;; We execute this function to import all subclasses of R class. This gives us 11 | ;; access to all application resources. 12 | (res/import-all) 13 | 14 | (defn notify-from-edit 15 | "Finds an EditText element with ID ::user-input in the given activity. Gets 16 | its contents and displays them in a toast if they aren't empty. We use 17 | resources declared in res/values/strings.xml." 18 | [activity] 19 | (let [^EditText input (.getText (find-view activity ::user-input))] 20 | (toast (if (empty? input) 21 | (res/get-string R$string/input_is_empty) 22 | (res/get-string R$string/your_input_fmt input)) 23 | :long))) 24 | 25 | ;; This is how an Activity is defined. We create one and specify its onCreate 26 | ;; method. Inside we create a user interface that consists of an edit and a 27 | ;; button. We also give set callback to the button. 28 | (defactivity test.leindroid.sample.MainActivity 29 | :key :main 30 | 31 | (onCreate [this bundle] 32 | (.superOnCreate this bundle) 33 | (neko.debug/keep-screen-on this) 34 | (on-ui 35 | (set-content-view! (*a) 36 | [:linear-layout {:orientation :vertical 37 | :layout-width :fill 38 | :layout-height :wrap} 39 | [:edit-text {:id ::user-input 40 | :hint "Type text here" 41 | :layout-width :fill}] 42 | [:button {:text R$string/touch_me ;; We use resource here, but could 43 | ;; have used a plain string too. 44 | :on-click (fn [_] (notify-from-edit (*a)))}]])))) 45 | -------------------------------------------------------------------------------- /sample/src/java/test/leindroid/sample/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package test.leindroid.sample; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.view.animation.Animation; 7 | import android.view.animation.AnimationUtils; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | import neko.App; 11 | 12 | import test.leindroid.sample.R; 13 | 14 | public class SplashActivity extends Activity { 15 | 16 | private static boolean firstLaunch = true; 17 | 18 | @Override 19 | public void onCreate(Bundle bundle) { 20 | super.onCreate(bundle); 21 | 22 | if (firstLaunch) { 23 | firstLaunch = false; 24 | setupSplash(); 25 | App.loadAsynchronously("test.leindroid.sample.MainActivity", 26 | new Runnable() { 27 | @Override 28 | public void run() { 29 | proceed(); 30 | }}); 31 | } else { 32 | proceed(); 33 | } 34 | } 35 | 36 | public void setupSplash() { 37 | setContentView(R.layout.splashscreen); 38 | 39 | TextView appNameView = (TextView)findViewById(R.id.splash_app_name); 40 | appNameView.setText(R.string.app_name); 41 | 42 | Animation rotation = AnimationUtils.loadAnimation(this, R.anim.splash_rotation); 43 | ImageView circleView = (ImageView)findViewById(R.id.splash_circles); 44 | circleView.startAnimation(rotation); 45 | } 46 | 47 | public void proceed() { 48 | startActivity(new Intent("test.leindroid.sample.MAIN")); 49 | finish(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/lein_droid/plugin.clj: -------------------------------------------------------------------------------- 1 | (ns lein-droid.plugin 2 | "Hosts middleware function to be applied early to the project map." 3 | (:require [leiningen.droid.classpath :refer [init-hooks]] 4 | [leiningen.droid.utils :refer [android-parameters]])) 5 | 6 | (defn middleware 7 | "Lein-droid's middleware adds default Android parameters to `:android` map, 8 | and also adds local Maven repositories from Android SDK." 9 | [project] 10 | (init-hooks) 11 | (android-parameters project)) 12 | -------------------------------------------------------------------------------- /src/leiningen/droid.clj: -------------------------------------------------------------------------------- 1 | ;; ## Clojure is simple. Android should also be. 2 | ;; This plugin is intended to make your Clojure/Android development as 3 | ;; seamless and efficient as when developing ordinar Clojure JVM programs. 4 | ;; 5 | (ns leiningen.droid 6 | (:refer-clojure :exclude [compile doall repl]) 7 | (:require clojure.pprint 8 | [leiningen.droid.aar :refer [extract-aar-dependencies]] 9 | [leiningen.droid.code-gen :refer [code-gen]]) 10 | (:use [leiningen.core.project :only [set-profiles]] 11 | [leiningen.core.main :only [abort]] 12 | [leiningen.help :only (subtask-help-for)] 13 | [leiningen.clean :only [clean]] 14 | [leiningen.droid.compile :only [compile]] 15 | [leiningen.droid 16 | [classpath :only [init-hooks]] 17 | [build :only [create-dex 18 | crunch-resources package-resources create-apk 19 | sign-apk zipalign-apk apk build jar aar]] 20 | [deploy :only [install run forward-port repl deploy local-repo]] 21 | [new :only [new init]] 22 | [test :only [local-test]] 23 | [utils :only [proj wrong-usage android-parameters sdk-sanity-check 24 | dev-build?]]])) 25 | 26 | (defn help 27 | "Shows the list of possible `lein droid` subtasks." 28 | ([]) ([droid-var] 29 | (println "lein-droid is a plugin for Clojure/Android development." 30 | (subtask-help-for nil droid-var)))) 31 | 32 | (defn pprint 33 | "Pretty-prints a representation of the project map." 34 | [project & keys] 35 | (if (seq keys) 36 | (clojure.pprint/pprint (select-keys project (map read-string keys))) 37 | (clojure.pprint/pprint project)) 38 | (flush)) 39 | 40 | (declare execute-subtask) 41 | 42 | (defn doall 43 | "Metatask. Performs all Android tasks from compilation to deployment." 44 | [{{:keys [library]} :android :as project} & device-args] 45 | (let [build-steps (if library ["build"] ["build" "apk" "deploy"])] 46 | (doseq [task build-steps] 47 | (execute-subtask project task device-args)))) 48 | 49 | (defn ^{:no-project-needed true 50 | :subtasks [#'new #'init #'code-gen #'compile #'create-dex 51 | #'crunch-resources #'package-resources 52 | #'create-apk #'sign-apk #'zipalign-apk 53 | #'install #'run #'forward-port #'repl 54 | #'build #'apk #'deploy #'doall #'help #'local-test 55 | #'jar #'pprint]} 56 | droid 57 | "Supertask for Android-related tasks (see `lein droid` for list)." 58 | ([project] 59 | (help #'droid)) 60 | ([project & [cmd & args]] 61 | (init-hooks) 62 | (if (#{"new" "help" "init"} cmd) 63 | (execute-subtask nil cmd args) 64 | (let [env-project (System/getenv "LEIN_DROID_PROJECT") 65 | project (if-not (empty? env-project) (proj env-project) project)] 66 | (cond (= cmd "pprint") (execute-subtask project cmd args) 67 | project (doto project 68 | sdk-sanity-check 69 | extract-aar-dependencies 70 | (execute-subtask cmd args)) 71 | :else (abort "Subtask" cmd "should be run from the project folder.")))))) 72 | 73 | (defn execute-subtask 74 | "Executes a subtask defined by `name` on the given project." 75 | [project name args] 76 | (case name 77 | ;; Standalone tasks 78 | "new" (if (< (count args) 2) 79 | (abort (wrong-usage "lein droid new" #'new)) 80 | (apply new args)) 81 | "init" (init (.getAbsolutePath (clojure.java.io/file "."))) 82 | "code-gen" (code-gen project) 83 | "compile" (compile project) 84 | "create-dex" (create-dex project) 85 | "crunch-resources" (crunch-resources project) 86 | "package-resources" (package-resources project) 87 | "create-apk" (create-apk project) 88 | "sign-apk" (sign-apk project) 89 | "zipalign-apk" (zipalign-apk project) 90 | "install" (apply install project args) 91 | "run" (apply run project args) 92 | "forward-port" (apply forward-port project args) 93 | "repl" (repl project) 94 | "clean" (clean project) 95 | "local-repo" (local-repo project) 96 | 97 | ;; Test tasks 98 | "local-test" (apply local-test project args) 99 | 100 | ;; Meta tasks 101 | "build" (build project) 102 | "apk" (apk project) 103 | "deploy" (apply deploy project args) 104 | "doall" (apply doall project args) 105 | "jar" (jar project) 106 | "aar" (aar project) 107 | 108 | ;; Help tasks 109 | "pprint" (apply pprint project args) 110 | "help" (help #'droid) 111 | 112 | (println "Subtask is not recognized:" name 113 | (subtask-help-for nil #'droid)))) 114 | -------------------------------------------------------------------------------- /src/leiningen/droid/aar.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.droid.aar 2 | "Utilities for manipulating Android package format (AAR)." 3 | (:require [clojure.java.io :as io] 4 | [clojure.edn :as edn] 5 | [leiningen.droid.utils :refer [get-dependencies]] 6 | [leiningen.core.main :refer [debug]]) 7 | (:import java.io.File 8 | net.lingala.zip4j.core.ZipFile)) 9 | 10 | (defn- get-aar-dependencies 11 | "Returns a list of artifact dependencies that have `aar` extension." 12 | [project] 13 | (let [deps (get-dependencies project)] 14 | (for [[[art-id ver & opts :as dep]] deps 15 | :let [opts (apply hash-map opts)] 16 | :when (= (:extension opts) "aar")] 17 | dep))) 18 | 19 | (defn- str-dependency 20 | "Takes a dependency vector and returns its stringified version to be used in a 21 | file system." 22 | [dep] 23 | (-> (meta dep) 24 | :dependency 25 | .getArtifact 26 | str 27 | (.replace ":" "_"))) 28 | 29 | (defn extract-aar-dependencies 30 | "Unpacks all AAR dependencies of the project into the target directory." 31 | [{:keys [target-path] :as project}] 32 | (let [deps (set (get-aar-dependencies project)) 33 | aar-extracted-dir (io/file target-path "aar-extracted") 34 | ;; Read which AARs we already extracted to avoid doing it again. 35 | extracted-file (io/file aar-extracted-dir "extracted.edn") 36 | already-extracted (when (.exists ^File extracted-file) 37 | (edn/read-string (slurp extracted-file)))] 38 | (when-not (or (empty? deps) (= deps already-extracted)) 39 | (debug "Extracting AAR dependencies: " deps) 40 | (doseq [dep deps] 41 | (.extractAll (ZipFile. (:file (meta dep))) 42 | (str (io/file aar-extracted-dir (str-dependency dep))))) 43 | (spit extracted-file deps)))) 44 | 45 | (defn get-aar-files 46 | "Returns the list of files or directories specified by `subpath` extracted 47 | from each AAR dependency." 48 | [{:keys [target-path] :as project} & subpath] 49 | (let [aar-extracted-dir (io/file target-path "aar-extracted")] 50 | (for [dep (get-aar-dependencies project)] 51 | (apply io/file aar-extracted-dir (str-dependency dep) subpath)))) 52 | 53 | (defn get-aar-classes 54 | "Returns the list of all jars extracted from all AAR dependencies." 55 | [project] 56 | (let [classes-jars (get-aar-files project "classes.jar") 57 | libs-dirs (get-aar-files project "libs")] 58 | (concat (filter #(.exists ^File %) classes-jars) 59 | (mapcat #(.listFiles ^File %) libs-dirs)))) 60 | 61 | (defn get-aar-native-paths 62 | "Returns the list of existing paths to native libraries extracted from AAR 63 | dependencies." 64 | [project] 65 | (filter #(.exists ^File %) (get-aar-files project "jni"))) 66 | -------------------------------------------------------------------------------- /src/leiningen/droid/build.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.droid.build 2 | "A set of functions and subtasks responsible for building the 3 | Android project." 4 | (:refer-clojure :exclude [compile]) 5 | (:use [leiningen.core 6 | [main :only [debug info abort *debug*]]] 7 | [leiningen.droid 8 | [compile :only [compile]] 9 | [utils :only [get-sdk-android-jar sh dev-build? 10 | ensure-paths with-process read-password append-suffix 11 | create-debug-keystore read-project resolve-dependencies 12 | sdk-binary relativize-path get-sdk-annotations-jar 13 | get-resource-jars get-sdk-build-tools-path]]]) 14 | (:require [clojure.string :as str] 15 | [clojure.set :as set] 16 | [clojure.java.io :as io] 17 | leiningen.core.project 18 | [leiningen.droid 19 | [code-gen :refer [code-gen]] 20 | [aar :refer [get-aar-files]] 21 | [manifest :refer [get-package-name]] 22 | [sdk :as sdk]] 23 | [leiningen.jar :as jar] 24 | leiningen.javac leiningen.pom) 25 | (:import java.io.File 26 | net.lingala.zip4j.core.ZipFile 27 | net.lingala.zip4j.model.ZipParameters 28 | net.lingala.zip4j.util.Zip4jConstants)) 29 | 30 | ;; ### Build-related subtasks 31 | 32 | (defn- run-proguard-minifying 33 | "Run proguard on the compiled classes and dependencies, create an JAR with 34 | minimized and shaken classes." 35 | [{{:keys [external-classes-paths 36 | proguard-conf-path proguard-opts proguard-output-jar-path]} :android 37 | compile-path :compile-path :as project}] 38 | (info "Running Proguard...") 39 | (ensure-paths compile-path proguard-conf-path) 40 | (let [proguard-bin (sdk-binary project :proguard) 41 | android-jar (get-sdk-android-jar project) 42 | annotations (get-sdk-annotations-jar project) 43 | deps (resolve-dependencies project) 44 | external-paths (or external-classes-paths []) 45 | proguard-opts (or proguard-opts [])] 46 | (sh proguard-bin (str "@" proguard-conf-path) 47 | "-injars" (->> (concat [compile-path] deps external-paths) 48 | (map str) 49 | (str/join ":")) 50 | "-libraryjars" (->> [annotations android-jar] 51 | (map str) 52 | (str/join ":")) 53 | "-outjars" proguard-output-jar-path 54 | proguard-opts))) 55 | 56 | (defn- run-proguard-multidexing 57 | "Run proguard on the compiled classes and dependencies to determine which 58 | classes have to be kept in primary dex." 59 | [{{:keys [multi-dex-proguard-conf-path multi-dex-root-classes-path]} :android 60 | :as project} target-paths] 61 | (ensure-paths multi-dex-proguard-conf-path) 62 | (let [proguard-bin (sdk-binary project :proguard) 63 | android-jar (io/file (get-sdk-build-tools-path project) 64 | "lib" "shrinkedAndroid.jar")] 65 | (sh proguard-bin (str "@" multi-dex-proguard-conf-path) 66 | "-injars" (str/join ":" target-paths) 67 | "-libraryjars" (str android-jar) 68 | "-outjars" multi-dex-root-classes-path))) 69 | 70 | (defn- generate-main-dex-list 71 | "Creates a text file with the list of classes that should be included into 72 | primary dex." 73 | [{{:keys [multi-dex-root-classes-path multi-dex-main-dex-list-path]} :android 74 | :as project} 75 | target-paths] 76 | (run-proguard-multidexing project target-paths) 77 | (let [dx-jar (io/file (get-sdk-build-tools-path project) "lib" "dx.jar") 78 | builder (ProcessBuilder. 79 | ["java" "-cp" (str dx-jar) "com.android.multidex.MainDexListBuilder" 80 | multi-dex-root-classes-path (str/join ":" target-paths)]) 81 | process-name (.start builder) 82 | output (line-seq (io/reader (.getInputStream process-name))) 83 | writer (io/writer (io/file multi-dex-main-dex-list-path))] 84 | (binding [*out* writer] 85 | (doseq [line output] 86 | (println line))) 87 | (.waitFor process-name))) 88 | 89 | ;; Since the execution of `dx` takes a pretty lot of time we need to 90 | ;; ensure that its subprocess will be killed if user cancels the build 91 | ;; (sends SIGINT to leiningen). That is why we add a hook to the 92 | ;; runtime that will be triggered when Leiningen is closed. 93 | ;; 94 | (defn- run-dx 95 | "Run dex on the given target paths, each should be either a directory with 96 | .class files or a jar file." 97 | [{{:keys [out-dex-path force-dex-optimize dex-opts multi-dex 98 | multi-dex-root-classes-path multi-dex-main-dex-list-path]} :android :as project} 99 | target-paths] 100 | (if multi-dex 101 | (do (info "Creating multi DEX....") 102 | (generate-main-dex-list project target-paths)) 103 | (info "Creating DEX....")) 104 | (let [dx-bin (sdk-binary project :dx) 105 | options (or dex-opts []) 106 | no-optimize (if (and (not force-dex-optimize) (dev-build? project)) 107 | "--no-optimize" []) 108 | annotations (get-sdk-annotations-jar project) 109 | multi-dex (if multi-dex 110 | ["--multi-dex" "--main-dex-list" multi-dex-main-dex-list-path] 111 | [])] 112 | (with-process [proc (->> [dx-bin options "--dex" no-optimize multi-dex 113 | "--output" out-dex-path 114 | target-paths annotations] 115 | flatten 116 | (map str))] 117 | (.addShutdownHook (Runtime/getRuntime) (Thread. #(.destroy proc)))))) 118 | 119 | (defn create-dex 120 | "Creates a DEX file from the compiled .class files." 121 | [{{:keys [sdk-path external-classes-paths 122 | proguard-execute proguard-output-jar-path]} :android, 123 | compile-path :compile-path :as project}] 124 | (if proguard-execute 125 | (do 126 | (run-proguard-minifying project) 127 | (run-dx project [proguard-output-jar-path])) 128 | (let [deps (resolve-dependencies project) 129 | external-classes-paths (or external-classes-paths [])] 130 | (run-dx project (concat [compile-path] deps external-classes-paths))))) 131 | 132 | (defn build 133 | "Metatask. Compiles the project and creates DEX." 134 | [{{:keys [library]} :android :as project}] 135 | (doto project 136 | code-gen compile create-dex)) 137 | 138 | (defn jar 139 | "Metatask. Packages compiled Java files and Clojure sources into JAR. 140 | 141 | Same as `lein jar` but appends Android libraries to the classpath 142 | while compiling Java files." 143 | [project] 144 | (leiningen.javac/javac project) 145 | (jar/write-jar project (jar/get-jar-filename project) 146 | (#'jar/filespecs (dissoc project :java-source-paths))) 147 | (leiningen.pom/pom project)) 148 | 149 | (defn aar 150 | "Metatask. Packages library into AAR archive." 151 | [{{:keys [manifest-path res-path gen-path assets-paths]} :android 152 | :keys [name version target-path compile-path root] :as project}] 153 | (code-gen project) 154 | (.renameTo (io/file gen-path "R.txt") (io/file target-path "R.txt")) 155 | (leiningen.javac/javac project) 156 | ;; Remove unnecessary files 157 | (.delete ^File (io/file gen-path "R.java.d")) 158 | (let [package-name (get-package-name manifest-path) 159 | classes-path (apply io/file compile-path (str/split package-name #"\."))] 160 | (doseq [^File file (.listFiles ^File classes-path) 161 | :let [filename (.getName file)] 162 | :when (or (= filename "R.class") 163 | (re-matches #"R\$\w+\.class" filename))] 164 | (.delete file))) 165 | ;; Make a JAR 166 | (jar/write-jar project (io/file target-path "classes.jar") 167 | (#'jar/filespecs (dissoc project :java-source-paths))) 168 | ;; Finally create AAR file 169 | (let [zip (ZipFile. (io/file target-path (format "%s-%s.aar" name version))) 170 | params (doto (ZipParameters.) 171 | (.setCompressionMethod Zip4jConstants/COMP_STORE) 172 | ;; (.setDefaultFolderPath "target") 173 | (.setEncryptFiles false))] 174 | (.addFile zip (io/file manifest-path) params) 175 | (.addFile zip (io/file target-path "classes.jar") params) 176 | (.addFile zip (io/file target-path "R.txt") params) 177 | (.addFolder zip (io/file res-path) params) 178 | (when (.exists (io/file root "libs")) 179 | (.addFolder zip (io/file root "libs") params)) 180 | (doseq [path assets-paths 181 | :when (.exists (io/file path))] 182 | (.addFolder zip (io/file path) params))) 183 | (leiningen.pom/pom (assoc project :packaging "aar"))) 184 | 185 | ;; ### APK-related subtasks 186 | 187 | ;; Because of the AAPT bug we must turn paths to files here so that proper 188 | ;; canonical names are calculated. 189 | ;; 190 | (defn crunch-resources 191 | "Updates the pre-processed PNG cache. 192 | 193 | Calls `aapt` binary with the _crunch_ task." 194 | [{{:keys [res-path out-res-path]} :android :as project}] 195 | (info "Crunching resources...") 196 | (ensure-paths res-path) 197 | (let [aapt-bin (sdk-binary project :aapt) 198 | crunch (fn [src-dir target-dir] 199 | (sh aapt-bin "crunch -v" "-S" src-dir "-C" target-dir))] 200 | (doseq [aar (get-aar-files project) 201 | :when (.exists ^File (io/file aar "R.txt")) 202 | :let [out (io/file aar "out-res")]] 203 | (.mkdirs ^File out) 204 | (crunch (io/file aar "res") out)) 205 | (crunch (io/file res-path) (io/file out-res-path)))) 206 | 207 | (defn package-resources 208 | "Packages application resources." 209 | [{{:keys [sdk-path target-version manifest-path assets-paths res-path 210 | out-res-path external-res-paths out-res-pkg-path 211 | rename-manifest-package assets-gen-path]} :android :as project}] 212 | (info "Packaging resources...") 213 | (ensure-paths sdk-path manifest-path res-path) 214 | (let [aapt-bin (sdk-binary project :aapt) 215 | android-jar (get-sdk-android-jar sdk-path target-version) 216 | debug-mode (if (dev-build? project) ["--debug-mode"] []) 217 | ;; Only add `assets` directories when they are present. 218 | assets (mapcat #(when (.exists (io/file %)) ["-A" (str %)]) 219 | (concat assets-paths [assets-gen-path] 220 | (get-aar-files project "assets"))) 221 | aar-resources (for [res (get-aar-files project "res")] ["-S" res]) 222 | aar-crunched-resources (for [res (get-aar-files project "out-res") 223 | :when (.exists ^File res)] 224 | ["-S" res]) 225 | external-resources (for [res external-res-paths] ["-S" res])] 226 | (sh aapt-bin "package" "--no-crunch" "-f" debug-mode "--auto-add-overlay" 227 | "-M" manifest-path 228 | "-S" out-res-path 229 | "-S" res-path 230 | aar-crunched-resources 231 | aar-resources 232 | external-resources 233 | assets 234 | "-I" android-jar 235 | "-F" out-res-pkg-path 236 | "--generate-dependencies" 237 | (if rename-manifest-package 238 | ["--rename-manifest-package" rename-manifest-package] [])))) 239 | 240 | (defn create-apk 241 | "Creates a deployment-ready APK file. 242 | 243 | It is done by executing methods from ApkBuilder SDK class on the 244 | generated DEX-file and the resource package." 245 | [{{:keys [out-apk-path out-res-pkg-path resource-jars-paths]} :android 246 | :as project}] 247 | (info "Creating APK...") 248 | (ensure-paths out-res-pkg-path) 249 | (let [unaligned-path (append-suffix out-apk-path "unaligned") 250 | resource-jars (concat (get-resource-jars project) 251 | (map io/file resource-jars-paths))] 252 | (sdk/create-apk project 253 | :apk-name unaligned-path :resource-jars resource-jars))) 254 | 255 | (defn sign-apk 256 | "Signs APK file with the key taken from the keystore. 257 | 258 | Either a debug keystore key or a release key is used based on 259 | whether the build type is the debug one. Creates a debug keystore if 260 | it is missing." 261 | [{{:keys [out-apk-path sigalg use-debug-keystore 262 | keystore-path key-alias keypass storepass]} :android :as project}] 263 | (info "Signing APK with" keystore-path "...") 264 | (let [debug (or (dev-build? project) use-debug-keystore 265 | (.endsWith keystore-path "debug.keystore")) 266 | unaligned-path (append-suffix out-apk-path "unaligned") 267 | sigalg (or sigalg "SHA1withRSA")] 268 | (when (and debug (not (.exists (io/file keystore-path)))) 269 | ;; Create a debug keystore if there isn't one 270 | (create-debug-keystore keystore-path)) 271 | (ensure-paths unaligned-path keystore-path) 272 | (let [storepass (or (when debug "android") 273 | storepass 274 | (System/getenv "STOREPASS") 275 | (read-password "Enter storepass: ")) 276 | keypass (or (when debug "android") 277 | keypass 278 | (System/getenv "KEYPASS") 279 | (read-password "Enter keypass: "))] 280 | (sh "jarsigner" 281 | "-sigalg" sigalg 282 | "-digestalg" "SHA1" 283 | "-keystore" keystore-path 284 | "-storepass" storepass 285 | "-keypass" keypass 286 | unaligned-path key-alias)))) 287 | 288 | (defn zipalign-apk 289 | "Aligns resources locations on 4-byte boundaries in the APK file. 290 | 291 | Done by calling `zipalign` binary on APK file." 292 | [{{:keys [sdk-path out-apk-path]} :android :as project}] 293 | (info "Aligning APK...") 294 | (let [zipalign-bin (sdk-binary project :zipalign) 295 | unaligned-path (append-suffix out-apk-path "unaligned")] 296 | (ensure-paths unaligned-path) 297 | (.delete (io/file out-apk-path)) 298 | (sh zipalign-bin "4" unaligned-path out-apk-path))) 299 | 300 | (defn apk 301 | "Metatask. Crunches and packages resources, creates, signs and aligns an APK." 302 | [project] 303 | (doto project 304 | crunch-resources package-resources 305 | create-apk sign-apk zipalign-apk)) 306 | -------------------------------------------------------------------------------- /src/leiningen/droid/classpath.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.droid.classpath 2 | "Contains functions and hooks for Android-specific classpath 3 | manipulation." 4 | (:require [leiningen.droid.aar :refer [get-aar-classes]] 5 | [leiningen.droid.utils :refer [get-sdk-android-jar 6 | get-sdk-annotations-jar 7 | leiningen-2-p-7-or-later?]] 8 | [robert.hooke :refer [add-hook]]) 9 | (:import org.sonatype.aether.util.version.GenericVersionScheme)) 10 | 11 | ;; Since `dx` and `ApkBuilder` utilities fail when they are feeded 12 | ;; repeated jar-files, we need to make sure that JAR dependencies list 13 | ;; contains only unique jars. 14 | 15 | (defn remove-duplicate-dependencies 16 | "Filters project's dependency list for unique jars regardless of 17 | version or groupId. Android-patched version of Clojure is prefered 18 | over the other ones. For the rest the latest version is preferred." 19 | [dependencies] 20 | (let [tagged (for [[artifact version :as dep] dependencies] 21 | (let [[_ group name] (re-matches #"(.+/)?(.+)" (str artifact))] 22 | {:name name, :group group, :ver version, :original dep})) 23 | grouped (group-by :name tagged) 24 | scheme (GenericVersionScheme.)] 25 | (for [[name same-jars] grouped] 26 | ;; For Clojure jar choose only from Android-specific versions 27 | ;; (if there is at least one). 28 | (let [same-jars (if (= name "clojure") 29 | (let [droid-clojures (filter #(= (:group %) 30 | "org.clojure-android/") 31 | same-jars)] 32 | (if-not (empty? droid-clojures) 33 | droid-clojures 34 | same-jars)) 35 | same-jars)] 36 | (:original 37 | (reduce #(if (pos? (compare (.parseVersion scheme (or (:version %2) 38 | "0")) 39 | (.parseVersion scheme (or (:version %1) 40 | "0")))) 41 | %2 %1) 42 | same-jars)))))) 43 | 44 | (defn- dependencies-hook 45 | "Takes the original `get-dependencies` function and arguments to it. 46 | Removes duplicate entries from the result when resolving project 47 | dependencies." 48 | [f dependency-key & rest] 49 | (let [[managed-deps project & rest] (if (leiningen-2-p-7-or-later?) 50 | rest (cons nil rest)) 51 | all-deps (if (leiningen-2-p-7-or-later?) 52 | (apply f dependency-key managed-deps project rest) 53 | (apply f dependency-key project rest))] 54 | (if (= dependency-key :dependencies) 55 | ;; aether/dependency-files expects a map but uses keys only, 56 | ;; so we transform a list into a map with nil values. 57 | (zipmap (remove-duplicate-dependencies (keys all-deps)) 58 | (repeat nil)) 59 | all-deps))) 60 | 61 | (defn- resolve-dependencies-hook 62 | "Takes the original `resolve-dependencies` function and arguments to it. 63 | Appends jar files extracted from AAR dependencies." 64 | [f dependency-key project & rest] 65 | (let [deps (apply f dependency-key project rest)] 66 | (if (= dependency-key :dependencies) 67 | (concat deps (get-aar-classes project)) 68 | deps))) 69 | 70 | (defn- resolve-managed-dependencies-hook 71 | "Takes the original `resolve-managed-dependencies` function and arguments to 72 | it. Appends jar files extracted from AAR dependencies." 73 | [f dependency-key managed-dependency-key project & rest] 74 | (let [deps (apply f dependency-key managed-dependency-key project rest)] 75 | (if (= dependency-key :dependencies) 76 | (->> (concat deps (get-aar-classes project)) 77 | ;; resolve-managed-dependencies is called multiple times. We must 78 | ;; dedupe already added dependencies. 79 | (group-by str) vals (map first)) 80 | deps))) 81 | 82 | ;; We also have to manually attach Android SDK libraries to the 83 | ;; classpath. The reason for this is that Leiningen doesn't handle 84 | ;; external dependencies at the high level, and Android jars are not 85 | ;; distributed in a convenient fashion (using Maven repositories). To 86 | ;; solve this we hack into `get-classpath` function. 87 | 88 | (defn classpath-hook 89 | "Takes the original `get-classpath` function and the project map, 90 | extracting the path to the Android SDK and the target version from it. 91 | Then the path to the actual `android.jar` file is constructed and 92 | appended to the rest of the classpath list." 93 | [f {{:keys [external-classes-paths]} :android :as project}] 94 | (let [classpath (f project) 95 | result (conj (concat classpath external-classes-paths) 96 | (get-sdk-android-jar project) 97 | (get-sdk-annotations-jar project))] 98 | result)) 99 | 100 | (defn init-hooks [] 101 | (add-hook #'leiningen.core.classpath/get-dependencies #'dependencies-hook) 102 | (add-hook #'leiningen.core.classpath/resolve-dependencies #'resolve-dependencies-hook) 103 | (when (leiningen-2-p-7-or-later?) 104 | (add-hook (resolve 'leiningen.core.classpath/resolve-managed-dependencies) 105 | #'resolve-managed-dependencies-hook)) 106 | (add-hook #'leiningen.core.classpath/get-classpath #'classpath-hook)) 107 | -------------------------------------------------------------------------------- /src/leiningen/droid/code_gen.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.droid.code-gen 2 | "Tasks and related functions for build-specific code generation." 3 | (:require [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [clostache.parser :as clostache] 6 | [leiningen.core.main :refer [debug info abort]] 7 | [leiningen.droid.aar :refer [get-aar-files]] 8 | [leiningen.droid.manifest :refer [get-package-name generate-manifest]] 9 | [leiningen.droid.sideload :as sideload] 10 | [leiningen.droid.utils :refer [get-sdk-android-jar sdk-binary 11 | ensure-paths sh dev-build?]] 12 | [leiningen.new.templates :refer [slurp-resource]]) 13 | (:import java.io.File)) 14 | 15 | ;; ### BuildConfig.java generation 16 | 17 | (defn- java-type 18 | "Mapping of classes to type strings as they should appear in BuildConfig." 19 | [x] 20 | (condp = (type x) 21 | Boolean "boolean" 22 | String "String" 23 | Long "long" 24 | Double "double" 25 | (abort ":build-config only supports boolean, String, long and double types."))) 26 | 27 | (defn map-constants 28 | "Transform a map of constants return to form readable by Clostache." 29 | [constants] 30 | (map (fn [[k v]] 31 | (binding [*print-dup* true] 32 | {:key k 33 | :value (pr-str v) 34 | :type (java-type v)})) 35 | constants)) 36 | 37 | (defn generate-build-constants 38 | [{{:keys [manifest-path gen-path build-config rename-manifest-package]} 39 | :android, version :version :as project}] 40 | (ensure-paths manifest-path) 41 | (let [res (io/resource "templates/BuildConfig.java") 42 | package-name (get-package-name manifest-path) 43 | gen-package-path (apply io/file gen-path (str/split package-name #"\.")) 44 | application-id (or rename-manifest-package package-name) 45 | template-constants (-> (merge {"VERSION_NAME" version 46 | "APPLICATION_ID" application-id} 47 | build-config) 48 | map-constants)] 49 | (ensure-paths gen-package-path) 50 | (->> {:debug (dev-build? project) 51 | :package-name package-name 52 | :constants template-constants} 53 | (clostache/render (slurp-resource res)) 54 | (spit (io/file gen-package-path "BuildConfig.java"))))) 55 | 56 | ;; ### R.java generation 57 | 58 | (defn create-r-file 59 | "Generates R.java file given full symbols file, library symbols file and 60 | library package name. Symbols file are loaded from respective R.txt files." 61 | [full-symbols lib-r-txt lib-package gen-path] 62 | (debug "Generating R.java file for:" lib-package) 63 | (let [symbols (sideload/symbol-loader lib-r-txt) 64 | writer (sideload/symbol-writer (str gen-path) lib-package full-symbols)] 65 | (.load symbols) 66 | (.addSymbolsToWrite writer symbols) 67 | (.write writer))) 68 | 69 | (defn generate-r-files 70 | "Generates R.java files for the project and all dependency libraries, having 71 | R.txt for project and each library." 72 | [{{:keys [sdk-path gen-path manifest-path]} :android :as project}] 73 | (sideload/sideload-jars sdk-path) 74 | (let [full-r-txt (io/file gen-path "R.txt") 75 | full-symbols (sideload/symbol-loader full-r-txt)] 76 | (.load full-symbols) 77 | (dorun 78 | (map (fn [manifest, ^File r-txt] 79 | (when (.exists r-txt) 80 | (let [package-name (get-package-name manifest) 81 | lib-gen-path gen-path] 82 | (create-r-file full-symbols r-txt package-name lib-gen-path)))) 83 | (get-aar-files project "AndroidManifest.xml") 84 | (get-aar-files project "R.txt"))))) 85 | 86 | (defn generate-resource-code 87 | "Generates R.java files for both the project and the libraries." 88 | [{{:keys [sdk-path target-version manifest-path res-path gen-path 89 | out-res-path external-res-paths library]} :android 90 | java-only :java-only :as project}] 91 | (info "Generating R.java files...") 92 | (let [aapt-bin (sdk-binary project :aapt) 93 | android-jar (get-sdk-android-jar sdk-path target-version) 94 | manifest-file (io/file manifest-path) 95 | library-specific (if library ["--non-constant-id"] []) 96 | aar-resources (for [res (get-aar-files project "res")] ["-S" (str res)]) 97 | external-resources (for [res external-res-paths] ["-S" res])] 98 | (ensure-paths manifest-path res-path android-jar) 99 | (.mkdirs (io/file gen-path)) 100 | (.mkdirs (io/file out-res-path)) 101 | (sh aapt-bin "package" library-specific "-f" "-m" 102 | "-M" manifest-path 103 | "-S" out-res-path 104 | "-S" res-path 105 | aar-resources 106 | external-resources 107 | "-I" android-jar 108 | "-J" gen-path 109 | "--output-text-symbols" gen-path 110 | "--auto-add-overlay" 111 | "--generate-dependencies") 112 | ;; Finally generate R.java files having R.txt keys 113 | (when-not library 114 | (generate-r-files project)))) 115 | 116 | (defn code-gen 117 | "Generates R.java and builds a manifest with the appropriate version 118 | code and substitutions." 119 | [{{:keys [library]} :android :as project}] 120 | (doto project 121 | generate-manifest generate-resource-code 122 | generate-build-constants)) 123 | -------------------------------------------------------------------------------- /src/leiningen/droid/compile.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.droid.compile 2 | "This part of the plugin is responsible for the project compilation." 3 | (:refer-clojure :exclude [compile]) 4 | (:require [bultitude.core :as bultitude] 5 | [clojure.java.io :as io] 6 | [clojure.set :as set] 7 | [leiningen.compile :refer [stale-namespaces]] 8 | [leiningen.core.classpath :refer [get-classpath]] 9 | [leiningen.core.eval :as eval] 10 | [leiningen.core.main :refer [debug info abort]] 11 | [leiningen.droid.manifest :refer [get-package-name]] 12 | [leiningen.droid.utils :refer [ensure-paths dev-build?]] 13 | leiningen.javac) 14 | (:import java.util.regex.Pattern)) 15 | 16 | ;; ### Pre-compilation tasks 17 | 18 | (defn eval-in-project 19 | ([project form init] 20 | (eval/prep project) 21 | (eval/eval-in project 22 | `(do ~@(map (fn [[k v]] `(set! ~k ~v)) (:global-vars project)) 23 | ~init 24 | ~@(:injections project) 25 | ~form))) 26 | ([project form] (eval-in-project project form nil))) 27 | 28 | (defn save-data-readers-to-resource 29 | "Save project's *data-readers* value to application's resources so 30 | it can be later retrieved in runtime. This is necessary to be able 31 | to use data readers when developing in REPL on the device." 32 | [{{:keys [assets-gen-path]} :android :as project}] 33 | (.mkdirs (io/file assets-gen-path)) 34 | (eval-in-project 35 | project 36 | `(do (require 'clojure.java.io) 37 | (spit (clojure.java.io/file ~assets-gen-path "data_readers.clj") 38 | (into {} (map (fn [[k# v#]] 39 | [k# (symbol (subs (str v#) 2))]) 40 | clojure.core/*data-readers*)))))) 41 | 42 | ;; ### Compilation 43 | 44 | (defn namespaces-to-compile 45 | "Takes project and returns a set of namespaces that should be AOT-compiled." 46 | [{{:keys [aot aot-exclude-ns]} :android :as project}] 47 | (let [all-nses (bultitude/namespaces-on-classpath 48 | :classpath (map io/file (get-classpath project)) 49 | :ignore-unreadable? false) 50 | include (case aot 51 | :all (stale-namespaces (assoc project :aot :all)) 52 | :all-with-unused all-nses 53 | aot) 54 | exclude aot-exclude-ns 55 | 56 | {include-nses false, include-regexps true} 57 | (group-by #(instance? Pattern %) include) 58 | 59 | {exclude-nses false, exclude-regexps true} 60 | (group-by #(instance? Pattern %) exclude)] 61 | (->> (set/difference (set (map str (if (seq include-regexps) 62 | all-nses include-nses))) 63 | (set exclude-nses)) 64 | (filter (fn [ns] (if (seq include-regexps) 65 | (some #(re-matches % ns) include-regexps) 66 | true))) 67 | (remove (fn [ns] (if (seq exclude-regexps) 68 | (some #(re-matches % ns) exclude-regexps) 69 | false))) 70 | (concat (if (seq include-regexps) 71 | include-nses ())) 72 | (map symbol)))) 73 | 74 | (defn compile-clojure 75 | "Compiles Clojure files into .class files. 76 | 77 | If `:aot` project parameter equals `:all` then compiles the 78 | necessary dependencies. If `:aot` equals `:all-with-unused` then 79 | compiles all namespaces of the dependencies whether they were 80 | referenced in the code or not. The latter is useful for the 81 | REPL-driven development. 82 | 83 | Uses neko to set compilation flags. Some neko macros and 84 | subsequently project code depends on them to eliminate 85 | debug-specific code when building the release." 86 | [{{:keys [enable-dynamic-compilation start-nrepl-server 87 | manifest-path repl-device-port ignore-log-priority 88 | lean-compile skummet-skip-vars]} 89 | :android 90 | {:keys [nrepl-middleware]} :repl-options 91 | :as project}] 92 | (info "Compiling Clojure files...") 93 | (debug "Project classpath:" (get-classpath project)) 94 | (let [nses (namespaces-to-compile project) 95 | dev-build (dev-build? project) 96 | package-name (try (get-package-name manifest-path) 97 | (catch Exception ex nil)) 98 | opts (cond-> {:neko.init/release-build (not dev-build) 99 | :neko.init/start-nrepl-server start-nrepl-server 100 | :neko.init/nrepl-port repl-device-port 101 | :neko.init/enable-dynamic-compilation 102 | enable-dynamic-compilation 103 | :neko.init/ignore-log-priority ignore-log-priority 104 | :neko.init/nrepl-middleware (list 'quote nrepl-middleware) 105 | :neko.init/package-name package-name} 106 | (not dev-build) (assoc :elide-meta 107 | [:doc :file :line :column :added :author 108 | :static :arglists :forms]))] 109 | (info (format "Build type: %s, dynamic compilation: %s, remote REPL: %s." 110 | (if dev-build "debug" (if lean-compile "lean" "release")) 111 | (if (or dev-build start-nrepl-server 112 | enable-dynamic-compilation) 113 | "enabled" "disabled") 114 | (if (or dev-build start-nrepl-server) "enabled" "disabled"))) 115 | (let [form 116 | (if lean-compile 117 | `(let [lean-var?# (fn [var#] (not (#{~@skummet-skip-vars} 118 | (str var#))))] 119 | (binding [~'clojure.core/*lean-var?* lean-var?# 120 | ~'clojure.core/*lean-compile* true 121 | ~'clojure.core/*compiler-options* ~opts] 122 | (doseq [namespace# '~nses] 123 | (println "Compiling" namespace#) 124 | (clojure.core/compile namespace#)) 125 | (shutdown-agents))) 126 | `(binding [*compiler-options* ~opts] 127 | ;; If expectations is present, don't run it during compilation. 128 | (doseq [namespace# '~nses] 129 | (println "Compiling" namespace#) 130 | (clojure.core/compile namespace#)) 131 | (try (require 'expectations) 132 | ((resolve 'expectations/disable-run-on-shutdown)) 133 | (catch Throwable _# nil)) 134 | (shutdown-agents)))] 135 | (.mkdirs (io/file (:compile-path project))) 136 | (try (eval-in-project project form) 137 | (info "Compilation succeeded.") 138 | (catch Exception e 139 | (abort "Compilation failed.")))))) 140 | 141 | (defn compile 142 | "Compiles both Java and Clojure source files." 143 | [{{:keys [sdk-path gen-path lean-compile]} :android, 144 | java-only :java-only :as project}] 145 | (ensure-paths sdk-path) 146 | (let [project (-> project 147 | (update-in [:prep-tasks] (partial remove #{"compile"})))] 148 | (leiningen.javac/javac project) 149 | (when-not java-only 150 | (save-data-readers-to-resource project) 151 | (compile-clojure project)))) 152 | -------------------------------------------------------------------------------- /src/leiningen/droid/deploy.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.droid.deploy 2 | "Functions and subtasks that install and run the application on the 3 | device and manage its runtime." 4 | (:use [leiningen.core.main :only [debug info abort *debug*]] 5 | [leiningen.droid.manifest :only (get-launcher-activity 6 | get-package-name)] 7 | [leiningen.droid.utils :only [sh ensure-paths append-suffix 8 | prompt-user sdk-binary]] 9 | [reply.main :only (launch-nrepl)]) 10 | (:require [clojure.java.io :as io] 11 | [cemerick.pomegranate.aether :as aether] 12 | [reply.initialization :as reply-init])) 13 | 14 | (defn- device-list 15 | "Returns the list of currently attached devices." 16 | [adb-bin] 17 | (let [output (rest (sh adb-bin "devices"))] ;; Ignore the first line 18 | (remove nil? 19 | (map #(let [[_ serial type] (re-find #"([^\t]+)\t([^\t]+)" %)] 20 | (when serial 21 | {:serial serial, :type type})) 22 | output)))) 23 | 24 | (defn- choose-device 25 | "If there is only one device attached returns its serial number, 26 | otherwise prompts user to choose the device to work with. If no 27 | devices are attached aborts the execution." 28 | [adb-bin] 29 | (let [devices (device-list adb-bin)] 30 | (case (count devices) 31 | 0 (abort "No devices are attached.") 32 | 1 (:serial (first devices)) 33 | (do 34 | (dotimes [i (count devices)] 35 | (println (format "%d. %s\t%s" (inc i) (:serial (nth devices i)) 36 | (:type (nth devices i))))) 37 | (print (format "Enter the number 1..%d to choose the device: " 38 | (count devices))) 39 | (flush) 40 | (if-let [answer (try (Integer/parseInt (read-line)) 41 | (catch Exception ex))] 42 | (:serial (nth devices (dec answer))) 43 | (abort "Cannot recognize device number.")))))) 44 | 45 | (defn get-device-args 46 | "Returns a list of adb arguments that specify the device adb should be 47 | working against. Calls `choose-device` if `device-args` parameter is 48 | nil." 49 | [adb-bin device-args] 50 | (or device-args 51 | (list "-s" (choose-device adb-bin)))) 52 | 53 | (def ^{:doc "Messages which `adb install` prints as the result." 54 | :private true} 55 | adb-responses 56 | {"Success" :success 57 | "Failure [INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES]" 58 | :inconsistent-certificates}) 59 | 60 | (def ^:private uninstall-prompt 61 | (str "Certificates of the installed application and the application being " 62 | "installed mismatch.\nDo you want to uninstall the old application " 63 | "first? (y/n): ")) 64 | 65 | ;; Since `adb` command always returns exit code zero, we have to 66 | ;; manually parse its output to figure out what is going on. This is 67 | ;; why this subtask is full of low-level stuff. 68 | (defn install 69 | "Installs the APK on the only (or specified) device or emulator." 70 | [{{:keys [out-apk-path manifest-path rename-manifest-package]} 71 | :android :as project} & device-args] 72 | (info "Installing APK...") 73 | (let [adb-bin (sdk-binary project :adb) 74 | _ (ensure-paths out-apk-path) 75 | device (get-device-args adb-bin device-args) 76 | output (java.io.StringWriter.)] 77 | ;; Rebind *out* to get the output `adb` produces. 78 | (binding [*out* output, *debug* true] 79 | (sh adb-bin device "install" "-r" out-apk-path)) 80 | (let [output (str output) 81 | response (some 82 | adb-responses 83 | (.split output (System/getProperty "line.separator")))] 84 | (case response 85 | :success (debug output) 86 | 87 | :inconsistent-certificates 88 | (let [resp (prompt-user uninstall-prompt) 89 | package-name (or rename-manifest-package 90 | (get-package-name manifest-path))] 91 | (if (.equalsIgnoreCase "y" resp) 92 | (do 93 | (sh adb-bin device "uninstall" package-name) 94 | (sh adb-bin device "install" out-apk-path)) 95 | (abort "Cannot proceed with installation."))) 96 | 97 | (do (info output) 98 | (abort "Abort execution.")))))) 99 | 100 | (defn run 101 | "Launches the installed APK on the connected device." 102 | [{{:keys [manifest-path launch-activity]} :android :as project} 103 | & device-args] 104 | (ensure-paths manifest-path) 105 | (when-let [activity (or launch-activity (get-launcher-activity project))] 106 | (info "Launching APK...") 107 | (let [adb-bin (sdk-binary project :adb) 108 | device (get-device-args adb-bin device-args)] 109 | (sh adb-bin device "shell" "am" "start" "-n" activity)))) 110 | 111 | (defn forward-port 112 | "Binds a port on the local machine to the port on the device. 113 | 114 | This allows to connect to the remote REPL from the current machine." 115 | [{{:keys [repl-device-port repl-local-port]} :android, root :root :as project} 116 | & device-args] 117 | (info "Binding device port" repl-device-port 118 | "to local port" repl-local-port "...") 119 | (spit (io/file root ".nrepl-port") repl-local-port) 120 | (let [adb-bin (sdk-binary project :adb) 121 | device (get-device-args adb-bin device-args)] 122 | (sh adb-bin device "forward" 123 | (str "tcp:" repl-local-port) 124 | (str "tcp:" repl-device-port)))) 125 | 126 | (defn default-init 127 | "Substitution for REPLy's own `default-init-function`." 128 | [{:keys [custom-help] :as options}] 129 | `(do 130 | ~@reply-init/prelude 131 | 132 | (use '[clojure.repl :only ~'[source apropos dir doc pst find-doc]]) 133 | (use '[clojure.pprint :only ~'[pp pprint]]) 134 | 135 | (defn ~'help 136 | "Prints a list of helpful commands." 137 | [] 138 | (println " Exit: Control+D or (exit) or (quit)") 139 | (println " Commands: (user/help)") 140 | (println " Docs: (doc function-name-here)") 141 | (println " (find-doc \"part-of-name-here\")") 142 | (println " Source: (source function-name-here)")) 143 | 144 | (user/help) 145 | 146 | nil)) 147 | 148 | (defn repl 149 | "Connects to a remote nREPL server on the device using REPLy." 150 | [{{:keys [repl-local-port]} :android}] 151 | (with-redefs [reply-init/default-init-code default-init] 152 | (launch-nrepl {:attach (str "localhost:" repl-local-port)}))) 153 | 154 | (defn deploy 155 | "Metatask. Runs `install, `run`, `forward-port`." 156 | [project & device-args] 157 | (let [adb-bin (sdk-binary project :adb) 158 | device (get-device-args adb-bin device-args)] 159 | (apply install project device) 160 | (apply run project device) 161 | (apply forward-port project device))) 162 | 163 | (defn local-repo 164 | "Install the generated AAR package to the local Maven repository." 165 | [{:keys [target-path name group version root] :as project}] 166 | (leiningen.pom/pom (assoc project :packaging "aar")) 167 | (let [aar-file (io/file target-path (format "%s-%s.aar" name version))] 168 | (ensure-paths aar-file) 169 | (->> {[:extension "pom"] (io/file root "pom.xml") 170 | [:extension "aar"] aar-file} 171 | (#'aether/artifacts-for [(symbol group name) version]) 172 | (aether/install-artifacts :files)))) 173 | -------------------------------------------------------------------------------- /src/leiningen/droid/manifest.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.droid.manifest 2 | "Contains functions to manipulate AndroidManifest.xml file" 3 | (:require [clojure.data.zip.xml :refer :all] 4 | [clojure.xml :as xml] 5 | [clojure.java.io :as jio] 6 | [clojure.string :as str] 7 | [clojure.zip :refer [up xml-zip]] 8 | [clostache.parser :as clostache] 9 | [leiningen.core.main :refer [info debug abort]] 10 | [leiningen.droid.aar :refer [get-aar-files]] 11 | [leiningen.droid.utils :refer [dev-build?]] 12 | [leiningen.release :refer [parse-semantic-version]]) 13 | (:import com.android.manifmerger.ManifestMerger 14 | com.android.manifmerger.MergerLog 15 | [com.android.utils StdLogger StdLogger$Level] 16 | java.io.File)) 17 | 18 | ;; ### Constants 19 | 20 | ;; Name of the category for the launcher activities. 21 | (def ^{:private true} launcher-category "android.intent.category.LAUNCHER") 22 | 23 | ;; Attribute name for target SDK version. 24 | (def ^:private target-sdk-attribute (keyword :android:targetSdkVersion)) 25 | 26 | ;; Attribute name for minimal SDK version. 27 | (def ^:private min-sdk-attribute (keyword :android:minSdkVersion)) 28 | 29 | ;; ### Local functions 30 | 31 | (defn- load-manifest 32 | "Parses given XML manifest file and creates a zipper from it." 33 | [manifest-path] 34 | (xml-zip (xml/parse manifest-path))) 35 | 36 | (defn- get-all-launcher-activities 37 | "Returns a list of zipper trees of Activities which belong to the 38 | _launcher_ category." 39 | [manifest] 40 | (xml-> manifest :application :activity :intent-filter :category 41 | (attr= :android:name launcher-category))) 42 | 43 | ;; ### Manifest parsing and data extraction 44 | 45 | (defn get-package-name 46 | "Returns the name of the application's package." 47 | [manifest-path] 48 | (first (xml-> (load-manifest manifest-path) (attr :package)))) 49 | 50 | (defn get-launcher-activity 51 | "Returns the package-qualified name of the first activity from the 52 | manifest that belongs to the _launcher_ category." 53 | [{{:keys [manifest-path rename-manifest-package]} :android}] 54 | (let [manifest (load-manifest manifest-path) 55 | [activity-name] (some-> manifest 56 | get-all-launcher-activities 57 | first 58 | up up 59 | (xml-> (attr :android:name))) 60 | pkg-name (first (xml-> manifest (attr :package)))] 61 | (when activity-name 62 | (str (or rename-manifest-package pkg-name) "/" 63 | (if (.startsWith activity-name ".") 64 | (str pkg-name activity-name) 65 | activity-name))))) 66 | 67 | (defn get-target-sdk-version 68 | "Extracts the target SDK version from the provided manifest file. If 69 | target SDK is not specified returns minimal SDK." 70 | [manifest-path] 71 | (let [[uses-sdk] (xml-> (load-manifest manifest-path) :uses-sdk)] 72 | (or (first (xml-> uses-sdk (attr target-sdk-attribute))) 73 | (first (xml-> uses-sdk (attr min-sdk-attribute)))))) 74 | 75 | ;; ### Manifest templating 76 | 77 | (def ^:private version-bit-sizes 78 | "Amount of bits allocated for each version bucket." 79 | [9 9 9 5]) 80 | 81 | (def ^:private version-maximums 82 | "Maximum values per each version bucket." 83 | (mapv (partial bit-shift-left 1) version-bit-sizes)) 84 | 85 | (def ^:private version-coefficients 86 | "Each part of the version number will be multiplied by the respective 87 | coefficient, all of which are calculated here." 88 | (->> version-bit-sizes 89 | (reductions +) 90 | (mapv (fn [offset] (bit-shift-left 1 (- 32 offset)))))) 91 | 92 | (defn- assert> 93 | "Asserts that a>b in version segments" 94 | [a b] 95 | (when-not (> a b) 96 | (abort (format "Version number segment too large to fit in the 97 | version-code scheme: %s > %s, maximum version in each segment is %s" 98 | b a (str/join "." version-maximums)))) 99 | b) 100 | 101 | (defn version-code 102 | "Given a version map containing :major :minor :patch 103 | :build and :priority version numbers, returns an integer which is 104 | guaranteed to be greater for semantically larger version numbers. 105 | 106 | Splitting the 32 bit version code into 5 segments such that each 107 | semantically greater version will have a larger version code. The 108 | segments represent major, minor, patch, build and package 109 | priority (multiple builds of the same android apk where one takes 110 | precedence over another, for instance in the case where higher 111 | resolution assets are available, but a fallback is made available 112 | for devices which do not support the configuration). 113 | 114 | Largest possible version number: v512.512.512 (32)" 115 | [version-map] 116 | (->> version-map 117 | ((juxt :major :minor :patch :priority)) 118 | (map (fnil assert> 0 0) version-maximums) 119 | (map * version-coefficients) 120 | (reduce +))) 121 | 122 | (defn merge-manifests 123 | "Merges the main application manifest file with manifests from AAR files." 124 | [{{:keys [manifest-path manifest-main-app-path]} :android :as project}] 125 | (let [merger (ManifestMerger. (MergerLog/wrapSdkLog 126 | (StdLogger. StdLogger$Level/VERBOSE)) nil) 127 | lib-manifests (get-aar-files project "AndroidManifest.xml")] 128 | (debug "Merging secondary manifests:" lib-manifests) 129 | (.process merger (jio/file manifest-path) (jio/file manifest-main-app-path) 130 | (into-array File lib-manifests) nil nil))) 131 | 132 | (defn generate-manifest 133 | "If a :manifest-template-path is specified, perform template substitution with 134 | the values in :android :manifest, including the version-name and version-code 135 | which are automatically generated, placing the output in :manifest-path." 136 | [{{:keys [manifest-path manifest-template-path manifest-options manifest-main-app-path 137 | target-version]} :android, version :version :as project}] 138 | (info "Generating manifest...") 139 | (let [full-manifest-map (merge {:version-name version 140 | :version-code (-> version 141 | parse-semantic-version 142 | version-code) 143 | :target-version target-version 144 | :debug-build (dev-build? project)} 145 | manifest-options)] 146 | (jio/make-parents manifest-path) 147 | (->> full-manifest-map 148 | (clostache/render (slurp manifest-template-path)) 149 | (spit manifest-main-app-path)) 150 | (merge-manifests project))) 151 | -------------------------------------------------------------------------------- /src/leiningen/droid/new.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.droid.new 2 | "Provides tasks for creating a new project or initialiaing plugin 3 | support in an existing one." 4 | (:require [clojure.string :as string] 5 | [clojure.java.io :as io] 6 | [leiningen.core.main :refer [info abort]] 7 | [leiningen.core.project :as project] 8 | [leiningen.droid.manifest :refer [get-target-sdk-version]] 9 | [leiningen.droid.utils :refer [get-dependencies]] 10 | [leiningen.new.templates :refer [render-text slurp-resource 11 | sanitize ->files]])) 12 | 13 | (defn renderer 14 | "Taken from lein-newnew. 15 | 16 | Create a renderer function that looks for mustache templates in the 17 | right place given the name of your template. If no data is passed, the 18 | file is simply slurped and the content returned unchanged." 19 | [name] 20 | (fn [template & [data]] 21 | (let [res (io/resource (str name "/" (sanitize template)))] 22 | (if data 23 | (render-text (slurp-resource res) data) 24 | (io/input-stream res))))) 25 | 26 | (defn package-to-path [package-name] 27 | (string/replace package-name #"\." "/")) 28 | 29 | (defn package-name-valid? [package-name] 30 | (and (not (.startsWith package-name ".")) 31 | (> (.indexOf package-name ".") -1) 32 | (= (.indexOf package-name "-") -1))) 33 | 34 | (defn- latest-version 35 | "Downloads the latest version of the given artifact symbol, and returns the 36 | version string." 37 | [artifact default] 38 | (let [version (try (->> (get-dependencies 39 | {:dependencies [[artifact "RELEASE"]] 40 | :repositories project/default-repositories}) 41 | keys 42 | (some #(when (= (first %) artifact) %)) 43 | second) 44 | (catch Exception ex nil))] 45 | (if version 46 | (do (info "Found" artifact version) 47 | version) 48 | (do (info "Couldn't resolve the latest" artifact 49 | "version, using default" default) 50 | default)))) 51 | 52 | (defn- current-plugin-version 53 | "Returns the version of this very lein-droid plugin currently being run." 54 | [] 55 | (try (->> (io/resource "META-INF/maven/lein-droid/lein-droid/pom.properties") 56 | io/reader line-seq 57 | (keep #(second (re-matches #"^version=(.+)" %))) 58 | (some identity)) 59 | (catch Exception ex nil))) 60 | 61 | (defn init 62 | "Creates project.clj file within an existing Android library folder. 63 | 64 | Presumes default directory names (like src, res and gen) and 65 | AndroidManifest.xml file to be already present in the project." 66 | [current-dir] 67 | (let [manifest (io/file current-dir "AndroidManifest.xml")] 68 | (when-not (.exists manifest) 69 | (abort "ERROR: AndroidManifest.xml not found - have to be in an existing" 70 | "Android project. Use `lein droid new` to create a new project.")) 71 | (let [manifest-path (.getAbsolutePath manifest) 72 | data {:name (.getName (io/file current-dir)) 73 | :target-sdk (or (get-target-sdk-version manifest-path) "15") 74 | :lein-droid-version (or (current-plugin-version) "0.4.3")} 75 | render (renderer "templates")] 76 | (info "Creating project.clj...") 77 | (io/copy (render "library.project.clj" data) 78 | (io/file current-dir "project.clj"))))) 79 | 80 | (defn new-library 81 | "Creates new Android library." 82 | [library-name package-name data] 83 | (let [render (renderer "templates")] 84 | (info "Creating library" library-name "...") 85 | (->files 86 | data 87 | "assets" 88 | [".gitignore" (render "gitignore")] 89 | ["LICENSE" (render "LICENSE")] 90 | ["README.md" (render "README.library.md" data)] 91 | ["AndroidManifest.template.xml" (render "AndroidManifest.library.xml" data)] 92 | ["project.clj" (render "library.project.clj" data)] 93 | ["res/values/strings.xml" (render "strings.library.xml" data)] 94 | ["src/java/{{path}}/Util.java" (render "Util.java" data)] 95 | ["src/clojure/{{path}}/main.clj" (render "core.clj" data)]))) 96 | 97 | (defn new-application 98 | "Creates new Android application." 99 | [project-name package-name data] 100 | (let [render (renderer "templates")] 101 | (info "Creating project" project-name "...") 102 | (->files 103 | data 104 | "assets" 105 | [".gitignore" (render "gitignore")] 106 | ["LICENSE" (render "LICENSE" data)] 107 | ["README.md" (render "README.md" data)] 108 | ["AndroidManifest.template.xml" (render "AndroidManifest.template.xml" data)] 109 | ["project.clj" (render "project.clj" data)] 110 | ["build/proguard-minify.cfg" (render "proguard_minify.cfg" data)] 111 | ["build/proguard-multi-dex.cfg" (render "proguard_multi_dex.cfg" data)] 112 | ["res/drawable-hdpi/splash_circle.png" (render "splash_circle.png")] 113 | ["res/drawable-hdpi/splash_droid.png" (render "splash_droid.png")] 114 | ["res/drawable-hdpi/splash_hands.png" (render "splash_hands.png")] 115 | ["res/drawable-hdpi/ic_launcher.png" (render "ic_launcher_hdpi.png")] 116 | ["res/drawable-mdpi/ic_launcher.png" (render "ic_launcher_mdpi.png")] 117 | ["res/drawable/splash_background.xml" (render "splash_background.xml")] 118 | ["res/anim/splash_rotation.xml" (render "splash_rotation.xml")] 119 | ["res/layout/splashscreen.xml" (render "splashscreen.xml")] 120 | ["res/values/strings.xml" (render "strings.xml" data)] 121 | ["src/java/{{path}}/SplashActivity.java" (render "SplashActivity.java" data)] 122 | ["src/clojure/{{path}}/main.clj" (render "main.clj" data)]))) 123 | 124 | (defn new 125 | "Creates new Android project given the project's name and package name." 126 | [project-name package-name & options] 127 | (when-not (package-name-valid? package-name) 128 | (abort "ERROR: Package name should have at least two levels and" 129 | "not contain hyphens (you can replace them with underscores).")) 130 | (info "Resolving latest artifact versions...") 131 | (let [options (apply hash-map options) 132 | data {:name project-name 133 | :package package-name 134 | :package-sanitized (sanitize package-name) 135 | :path (package-to-path (sanitize package-name)) 136 | :activity (get options ":activity" "MainActivity") 137 | :target-sdk (get options ":target-sdk" "18") 138 | :min-sdk (get options ":min-sdk" "15") 139 | :app-name (get options ":app-name" project-name) 140 | :library (get options ":library" false) 141 | :new-project true 142 | :lein-droid-version (or (current-plugin-version) "0.4.3") 143 | :clojure-version (latest-version 'org.clojure-android/clojure "1.7.0-r3") 144 | :neko-version (latest-version 'neko "4.0.0-alpha5") 145 | :skummet-version (latest-version 'org.skummet/clojure "1.7.0-r1")}] 146 | (if (= (:library data) "true") 147 | (new-library project-name package-name data) 148 | (new-application project-name package-name data)))) 149 | -------------------------------------------------------------------------------- /src/leiningen/droid/sdk.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.droid.sdk 2 | "Functions to interact with Android SDK tools." 3 | (:use [leiningen.core.main :only [debug abort]]) 4 | (:require [leiningen.droid.aar :refer [get-aar-native-paths]] 5 | [leiningen.droid.sideload :as sideload] 6 | [clojure.java.io :as io]) 7 | (:import java.io.File)) 8 | 9 | (defn- get-unpacked-natives-paths 10 | "Returns paths to unpacked native libraries if they exist, nil otherwise." 11 | [] 12 | (let [path "target/native/linux/"] 13 | (when (.exists (io/file path)) 14 | [path]))) 15 | 16 | (defn create-apk 17 | "Delegates APK creation to ApkBuilder class in sdklib.jar." 18 | [{{:keys [sdk-path out-res-pkg-path out-dex-path native-libraries-paths]} 19 | :android :as project} & {:keys [apk-name resource-jars]}] 20 | (sideload/sideload-jars sdk-path) 21 | (let [apkbuilder (sideload/apk-builder apk-name out-res-pkg-path out-dex-path) 22 | all-native-libraries (concat native-libraries-paths 23 | (get-unpacked-natives-paths) 24 | (get-aar-native-paths project)) 25 | dexes (filter #(re-matches #".*dex" (.getName %)) 26 | (.listFiles (io/file out-dex-path)))] 27 | (when (seq resource-jars) 28 | (debug "Adding resource libraries: " resource-jars) 29 | (doseq [rj resource-jars] 30 | (.addResourcesFromJar apkbuilder rj))) 31 | (when (seq all-native-libraries) 32 | (debug "Adding native libraries: " all-native-libraries) 33 | (doseq [lib all-native-libraries] 34 | (.addNativeLibraries apkbuilder ^File (io/file lib)))) 35 | (if (seq dexes) 36 | (do 37 | (debug "Adding DEX files: " dexes) 38 | (doseq [dex dexes] 39 | (.addFile apkbuilder dex (.getName dex)))) 40 | (abort "No *.dex files found in " out-dex-path)) 41 | (.sealApk apkbuilder))) 42 | -------------------------------------------------------------------------------- /src/leiningen/droid/sideload.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.droid.sideload 2 | "Wrappers around classes and methods that we pull dynamically from jars in 3 | Android SDK." 4 | (:require [cemerick.pomegranate :as cp] 5 | [clojure.java.io :as io]) 6 | (:import [java.io File PrintStream])) 7 | 8 | (def sideload-jars 9 | "Dynamically adds jars from Android SDK on the classpath." 10 | (memoize (fn [sdk-path] 11 | (cp/add-classpath (io/file sdk-path "tools" "lib" "sdklib.jar"))))) 12 | 13 | (defn apk-builder 14 | "Uses reflection to make an ApkBuilder instance." 15 | [apk-name res-path dex-path] 16 | (let [apkbuilder-class (Class/forName "com.android.sdklib.build.ApkBuilder") 17 | constructor (. apkbuilder-class getConstructor 18 | (into-array [File File File String PrintStream]))] 19 | (.newInstance constructor (into-array [(io/file apk-name) (io/file res-path) 20 | nil nil nil])))) 21 | 22 | (defn symbol-loader 23 | "Uses reflection to make an SymbolLoader instance." 24 | [file] 25 | (let [sl-class (Class/forName "com.android.sdklib.internal.build.SymbolLoader") 26 | constructor (. sl-class getConstructor (into-array [File]))] 27 | (.newInstance constructor (into-array [(io/file file)])))) 28 | 29 | (defn symbol-writer 30 | "Uses reflection to make an SymbolLoader instance." 31 | [out-folder package-name full-symbols] 32 | (let [sl-class (Class/forName "com.android.sdklib.internal.build.SymbolLoader") 33 | sw-class (Class/forName "com.android.sdklib.internal.build.SymbolWriter") 34 | constructor (. sw-class getConstructor (into-array [String String sl-class]))] 35 | (.newInstance constructor (into-array Object [out-folder package-name full-symbols])))) 36 | -------------------------------------------------------------------------------- /src/leiningen/droid/test.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.droid.test 2 | (:refer-clojure :exclude [test]) 3 | (:require [bultitude.core :as b] 4 | [clojure.java.io :as io] 5 | [clojure.string :as str] 6 | [clojure.set :as set] 7 | [leiningen.core.classpath :as cp] 8 | [leiningen.droid.code-gen :as code-gen] 9 | [leiningen.droid.compile :as compile] 10 | [leiningen.droid.utils :as utils])) 11 | 12 | (defn local-test 13 | "Runs tests locally using Robolectric." 14 | [{{:keys [cloverage-exclude-ns]} :android :as project} & [mode]] 15 | (when-not (-> project :android :library) 16 | (code-gen/code-gen project)) 17 | (compile/compile project) 18 | (let [src-nses (b/namespaces-on-classpath 19 | :classpath (map io/file (distinct (:source-paths project))) 20 | :ignore-unreadable? false) 21 | src-nses (set/difference (set src-nses) 22 | (set (map symbol cloverage-exclude-ns))) 23 | test-nses (b/namespaces-on-classpath 24 | :classpath (map io/file (distinct (:test-paths project))) 25 | :ignore-unreadable? false) 26 | cpath (cp/get-classpath project) 27 | mode (or mode "clojuretest")] 28 | (binding [utils/*sh-print-output* true] 29 | (utils/sh "java" "-cp" (str/join ":" cpath) 30 | "coa.droid_test.internal.TestRunner" "-mode" mode 31 | ":src" (map str src-nses) 32 | ":test" (map str test-nses))))) 33 | -------------------------------------------------------------------------------- /src/leiningen/droid/utils.clj: -------------------------------------------------------------------------------- 1 | ;; Provides utilities for the plugin. 2 | ;; 3 | (ns leiningen.droid.utils 4 | (:require [leiningen.core.project :as pr] 5 | [leiningen.core.classpath :as cp] 6 | robert.hooke) 7 | (:use [clojure.java.io :only (file reader)] 8 | [leiningen.core.main :only (info debug abort *debug*)] 9 | [clojure.string :only (join)]) 10 | (:import [java.io File StringWriter])) 11 | 12 | ;; #### Convenient functions to run SDK binaries 13 | 14 | (defmacro ensure-paths 15 | "Checks if the given directories or files exist. Aborts Leiningen 16 | execution in case either of them doesn't or the value equals nil. 17 | 18 | We assume paths to be strings or lists/vectors. The latter case is 19 | used exclusively for Windows batch-files which are represented like 20 | `cmd.exe /C batch-file`, so we test third element of the list for 21 | the existence." 22 | [& paths] 23 | `(do 24 | ~@(for [p paths] 25 | `(cond (nil? ~p) 26 | (abort "The value of" (str '~p) "is nil. Abort execution.") 27 | 28 | (or 29 | (and (sequential? ~p) (not (.exists (file (nth ~p 2))))) 30 | (and (string? ~p) (not (.exists (file ~p))))) 31 | (abort "The path" ~p "doesn't exist. Abort execution."))))) 32 | 33 | (defn windows? 34 | "Returns true if we are running on Microsoft Windows" 35 | [] 36 | (= java.io.File/separator "\\")) 37 | 38 | (defn get-sdk-build-tools-path 39 | "Returns a path to the correct Android Build Tools directory." 40 | ([{{:keys [sdk-path build-tools-version]} :android}] 41 | (get-sdk-build-tools-path sdk-path build-tools-version)) 42 | ([sdk-path build-tools-version] 43 | (let [bt-root-dir (file sdk-path "build-tools") 44 | ;; build-tools directory contains a subdir which name we don't 45 | ;; know that has all the tools. Let's grab the last directory 46 | ;; inside build-tools/ and hope it is the one we need. 47 | bt-dir (or build-tools-version 48 | (->> (.list bt-root-dir) 49 | (filter #(.isDirectory (file bt-root-dir %))) 50 | sort last) 51 | (abort "Build tools not found." 52 | "Download them using the Android SDK Manager."))] 53 | (file bt-root-dir bt-dir)))) 54 | 55 | (defn sdk-binary-paths 56 | "Returns a map of relative paths to different SDK binaries for both 57 | Unix and Windows platforms." 58 | [sdk-path build-tools-version] 59 | (ensure-paths sdk-path) 60 | (let [build-tools (get-sdk-build-tools-path sdk-path build-tools-version)] 61 | {:dx {:unix (file build-tools "dx") 62 | :win (file build-tools "dx.bat")} 63 | :adb {:unix (file sdk-path "platform-tools" "adb") 64 | :win (file sdk-path "platform-tools" "adb.exe")} 65 | :aapt {:unix (file build-tools "aapt") 66 | :win (file build-tools "aapt.exe")} 67 | :zipalign {:unix (file build-tools "zipalign") 68 | :win (file build-tools "zipalign.exe")} 69 | :proguard {:unix (file sdk-path "tools" "proguard" "bin" "proguard.sh") 70 | :win (file sdk-path "tools" "proguard" "bin" "proguard.bat")}})) 71 | 72 | (defn sdk-binary 73 | "Given the project map and the binary keyword, returns either a full 74 | path to the binary as a string, or a vector with call to cmd.exe for 75 | batch-files." 76 | [{{:keys [sdk-path build-tools-version]} :android} binary-kw] 77 | (let [binary-str (-> (sdk-binary-paths sdk-path build-tools-version) 78 | (get-in [binary-kw (if (windows?) :win :unix)]) 79 | str)] 80 | (ensure-paths binary-str) 81 | (if (.endsWith binary-str ".bat") 82 | ["cmd.exe" "/C" binary-str] 83 | binary-str))) 84 | 85 | ;; ### Middleware section 86 | 87 | (defn absolutize 88 | "Taken from Leiningen source code. 89 | 90 | Absolutizes the `path` given `root` if it is relative. Leaves the 91 | path as is if it is absolute." 92 | [root path] 93 | (str (if (.isAbsolute (file path)) 94 | path 95 | (.getCanonicalPath (file root path))))) 96 | 97 | (defn absolutize-android-paths 98 | "Taken from Leiningen source code. 99 | 100 | Absolutizes all values with keys ending with `path` or `paths` in 101 | the `:android` map of the project." 102 | [{:keys [root android] :as project}] 103 | (assoc project :android 104 | (into {} (for [[key val] android] 105 | [key (cond (re-find #"-path$" (name key)) 106 | (absolutize root val) 107 | 108 | (re-find #"-paths$" (name key)) 109 | (map (partial absolutize root) val) 110 | 111 | :else val)])))) 112 | 113 | (defn get-default-android-params 114 | "Returns a map of the default android-specific parameters." 115 | [{root :root, name :name, target-path :target-path 116 | java-paths :java-source-paths}] 117 | {:out-dex-path target-path 118 | :proguard-execute false 119 | :proguard-conf-path "proguard.conf" 120 | :proguard-output-jar-path (file target-path "mininified-classes.jar") 121 | :multi-dex-root-classes-path (file target-path "root-classes.jar") 122 | :multi-dex-main-dex-list-path (file target-path "main-dex-list.txt") 123 | :manifest-path (file target-path "AndroidManifest.xml") 124 | :manifest-main-app-path (file target-path "AndroidManifest.app.xml") 125 | :manifest-template-path "AndroidManifest.template.xml" 126 | :manifest-options {:app-name "@string/app_name"} 127 | :res-path "res" 128 | :gen-path (file target-path "gen") 129 | :out-res-path (file target-path "res") 130 | :assets-paths ["assets"] 131 | :assets-gen-path (file target-path "assets-gen") 132 | :out-res-pkg-path (file target-path (str name ".ap_")) 133 | :out-apk-path (file target-path (str name ".apk")) 134 | :keystore-path (file (System/getProperty "user.home") 135 | ".android" "debug.keystore") 136 | :key-alias "androiddebugkey" 137 | :repl-device-port 9999 138 | :repl-local-port 9999 139 | :target-version 15}) 140 | 141 | (defn android-parameters 142 | "Merges project's `:android` map with default Android parameters and 143 | absolutizes paths in the `:android` map." 144 | [{:keys [android root] :as project}] 145 | (if-not (:sdk-path android) 146 | ;; :sdk-path might be nil when this function is called from middleware 147 | ;; before the :sdk-path is merged from some external profile. In this case 148 | ;; just do nothing to project map. 149 | project 150 | (let [android-params (merge (get-default-android-params project) android) 151 | sdk-path (absolutize root (:sdk-path android)) 152 | support-repo (file sdk-path "extras" "android" "m2repository") 153 | ps-repo (file sdk-path "extras" "google" "m2repository")] 154 | (-> project 155 | (update-in [:java-source-paths] conj (str (:gen-path android-params))) 156 | (update-in [:repositories] concat 157 | [["android-support" {:url (str "file://" support-repo)}] 158 | ["android-play-services" {:url (str "file://" ps-repo)}]]) 159 | (assoc :android android-params) 160 | absolutize-android-paths)))) 161 | 162 | ;; ### General utilities 163 | 164 | (defn read-project 165 | "Reads and initializes a Leiningen project and applies Android 166 | middleware to it." 167 | [project-file] 168 | (android-parameters (pr/init-project (pr/read (str project-file))))) 169 | 170 | (defn proj 171 | ([] (proj "sample/project.clj")) 172 | ([project-clj] 173 | (let [profiles (.split (or (System/getenv "LEIN_DROID_PROFILES") "dev") ",") 174 | project (read-project project-clj)] 175 | (if (seq profiles) 176 | (pr/set-profiles project 177 | (distinct 178 | (mapcat #(pr/expand-profile project (keyword %)) 179 | profiles))) 180 | project)))) 181 | 182 | (defn sdk-version-number 183 | "If version keyword is passed (for example, `:ics` or `:jelly-bean`), resolves 184 | it to the version number. Otherwise just returns the input." 185 | [kw-or-number] 186 | (if (keyword? kw-or-number) 187 | (case kw-or-number 188 | :ics 15 189 | :jelly-bean 18 190 | :kitkat 19 191 | :lollipop 21 192 | (abort "Unknown Android SDK version: " kw-or-number)) 193 | kw-or-number)) 194 | 195 | (defn get-sdk-platform-path 196 | "Returns a version-specific path to the Android platform tools." 197 | [sdk-root version] 198 | (str (file sdk-root "platforms" (str "android-" 199 | (sdk-version-number version))))) 200 | 201 | (defn get-sdk-android-jar 202 | "Returns a version-specific path to the `android.jar` SDK file." 203 | ([{{:keys [sdk-path target-version]} :android :as project}] 204 | (get-sdk-android-jar sdk-path target-version)) 205 | ([sdk-root version] 206 | (str (file (get-sdk-platform-path sdk-root version) "android.jar")))) 207 | 208 | (defn get-sdk-annotations-jar 209 | "Returns a path to annotations.jar file." 210 | [sdk-root-or-project] 211 | (let [sdk-root (if (map? sdk-root-or-project) 212 | (get-in sdk-root-or-project [:android :sdk-path]) 213 | sdk-root-or-project)] 214 | (str (file sdk-root "tools" "support" "annotations.jar")))) 215 | 216 | (declare leiningen-2-p-7-or-later?) 217 | (declare resolve-dependencies) 218 | 219 | (defn get-resource-jars 220 | "Get the list of dependency libraries that has `:use-resources true` 221 | in their definition." 222 | [{:keys [dependencies] :as project}] 223 | (let [res-deps (filter (fn [[_ _ & {:as options}]] 224 | (:use-resources options)) 225 | (:dependencies project)) 226 | mod-proj (assoc project :dependencies res-deps)] 227 | ;; Resolve dependencies with hooks disabled. 228 | (let [resolve-var (if (leiningen-2-p-7-or-later?) 229 | (resolve 'leiningen.core.classpath/resolve-managed-dependencies) 230 | (resolve 'leiningen.core.classpath/resolve-dependencies))] 231 | (with-redefs-fn {resolve-var (#'robert.hooke/original resolve-var)} 232 | ;; Call our wrapper resolve-dependencies function 233 | #(resolve-dependencies mod-proj))))) 234 | 235 | (defn sdk-sanity-check 236 | "Ensures that :sdk-path is present in the project, and necessary modules are 237 | installed." 238 | [{{:keys [sdk-path target-version]} :android :as project}] 239 | (ensure-paths sdk-path) 240 | (let [check (fn [name file] (when-not (.exists file) 241 | (abort name "is not installed. 242 | Please install it from your Android SDK manager.")))] 243 | (when-not target-version 244 | (abort ":target-version is not specified. Abort execution.")) 245 | (check (str "SDK platform " target-version) 246 | (file (get-sdk-platform-path sdk-path target-version))) 247 | (check "Android Support Repository" 248 | (file sdk-path "extras" "android" "m2repository")))) 249 | 250 | (def ^:dynamic *sh-print-output* 251 | "If true, print the output of the shell command regardless of *debug*." 252 | false) 253 | 254 | (defmacro with-process 255 | "Executes the subprocess specified in the binding list and applies 256 | `body` do it while it is running. The binding list consists of a var 257 | name for the process and the list of strings that represents shell 258 | command. 259 | 260 | After body is executed waits for a subprocess to finish, then checks 261 | the exit code. If code is not zero then prints the subprocess' 262 | output. If in DEBUG mode print both the command and it's output even 263 | for the successful run." 264 | [[process-name command] & body] 265 | `(do 266 | (apply debug ~command) 267 | (let [builder# (ProcessBuilder. ~command) 268 | _# (.redirectErrorStream builder# true) 269 | ~process-name (.start builder#) 270 | output# (line-seq (reader (.getInputStream ~process-name))) 271 | out-stream# (StringWriter.) 272 | print-output?# (or *debug* *sh-print-output*)] 273 | ~@body 274 | (doseq [line# output#] 275 | (if print-output?# 276 | (info line#) 277 | (binding [*out* out-stream#] 278 | (println line#)))) 279 | (.waitFor ~process-name) 280 | (when-not (and (= (.exitValue ~process-name) 0) 281 | (not print-output?#)) 282 | (info (str out-stream#))) 283 | (when-not (= (.exitValue ~process-name) 0) 284 | (abort "Abort execution.")) 285 | output#))) 286 | 287 | (defn sh 288 | "Executes the command given by `args` in a subprocess. Flattens the 289 | given list. Turns files into canonical paths." 290 | [& args] 291 | (let [str-args (for [arg (flatten args)] 292 | (if (instance? File arg) 293 | (.getCanonicalPath ^File arg) 294 | (str arg)))] 295 | (with-process [process str-args]))) 296 | 297 | (defn dev-build? 298 | "Checks the build type of the current project, assuming dev build if 299 | not a release build" 300 | [project] 301 | (not= (get-in project [:android :build-type]) :release)) 302 | 303 | (defn wrong-usage 304 | "Returns a string with the information about the proper function usage." 305 | ([task-name function-var] 306 | (wrong-usage task-name function-var 0)) 307 | ([task-name function-var arglist-number] 308 | (let [arglist (-> function-var 309 | meta :arglists (nth arglist-number)) 310 | argcount (count arglist) 311 | parametrify #(str "<" % ">") 312 | ;; Replace the destructuring construction after & with 313 | ;; [optional-args]. 314 | arglist (if (= (nth arglist (- argcount 2)) '&) 315 | (concat (map parametrify 316 | (take (- argcount 2) arglist)) 317 | ["[optional-args]"]) 318 | (map parametrify arglist))] 319 | (format "Wrong number of argumets. USAGE: %s %s" 320 | task-name (join (interpose " " arglist)))))) 321 | 322 | (defn prompt-user 323 | "Reads a string from the console until the newline character." 324 | [prompt] 325 | (print prompt) 326 | (flush) 327 | (read-line)) 328 | 329 | (defn read-password 330 | "Reads the password from the console without echoing the 331 | characters." 332 | [prompt] 333 | (if-let [console (System/console)] 334 | (join (.readPassword console prompt nil)) 335 | (prompt-user prompt))) 336 | 337 | (defn append-suffix 338 | "Appends a suffix to a filename, e.g. transforming `sample.apk` into 339 | `sample-signed.apk`" 340 | [filename suffix] 341 | (let [[_ without-ext ext] (re-find #"(.+)(\.\w+)" filename)] 342 | (str without-ext "-" suffix ext))) 343 | 344 | (defn create-debug-keystore 345 | "Creates a keystore for signing debug APK files." 346 | [keystore-path] 347 | (sh "keytool" "-genkey" "-v" 348 | "-keystore" keystore-path 349 | "-alias" "androiddebugkey" 350 | "-sigalg" "SHA1withRSA" 351 | "-keyalg" "RSA" 352 | "-keysize" "1024" 353 | "-validity" "365" 354 | "-keypass" "android" 355 | "-storepass" "android" 356 | "-dname" "CN=Android Debug,O=Android,C=US")) 357 | 358 | (defn relativize-path [^File dir ^File to-relativize] 359 | (.getPath (.relativize (.toURI dir) 360 | (.toURI to-relativize)))) 361 | 362 | ;; ### Compatibility 363 | 364 | (def leiningen-2-p-7-or-later? 365 | "Returns true if Leiningen version is 2.7 or later." 366 | (memoize 367 | (fn [] 368 | (let [[_ major minor] (re-matches #"(\d+)\.(\d+)\..+" 369 | (leiningen.core.main/leiningen-version)) 370 | major (Integer/parseInt major) 371 | minor (Integer/parseInt minor)] 372 | (or (> major 2) 373 | (and (= major 2) (>= minor 7))))))) 374 | 375 | (defn get-dependencies 376 | "Leiningen 2.7.0 introduced managed dependencies and broke 377 | `cp/get-dependencies`. We must handle both versions of the function." 378 | [project & args] 379 | (if (leiningen-2-p-7-or-later?) 380 | (apply cp/get-dependencies :dependencies :managed-dependencies project args) 381 | (apply cp/get-dependencies :dependencies project args))) 382 | 383 | (defn resolve-dependencies 384 | "Leiningen 2.7.0 introduced managed dependencies and deprecated 385 | `resolve-dependencies`." 386 | [project & args] 387 | (if (leiningen-2-p-7-or-later?) 388 | (apply (resolve 'leiningen.core.classpath/resolve-managed-dependencies) 389 | :dependencies :managed-dependencies project args) 390 | (apply (resolve 'leiningen.core.classpath/resolve-dependencies) 391 | :dependencies project args))) 392 | -------------------------------------------------------------------------------- /travis/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ $(lein with-profile travis pprint :version) =~ .*\-SNAPSHOT ]]; then 3 | lein deploy clojars 4 | fi 5 | -------------------------------------------------------------------------------- /travis/profiles.clj: -------------------------------------------------------------------------------- 1 | ;; These profiles should be used as profiles.clj on Travis CI. 2 | {:auth {:repository-auth {#"https://clojars.org/repo" 3 | {:username :env, :password :env}}} 4 | :travis {:plugins [[lein-pprint "1.1.1"]] 5 | :android {:sdk-path "/usr/local/android-sdk/"} 6 | :deploy-repositories [["releases" :clojars]]} 7 | :android-user {:dependencies [[cider/cider-nrepl "0.9.0-SNAPSHOT"]] 8 | :android {:aot-exclude-ns ["cider.nrepl.middleware.util.java.parser" 9 | "cider.nrepl" "cider-nrepl.plugin"]}}} 10 | 11 | --------------------------------------------------------------------------------