diff --git a/.gitignore b/.gitignore index b90e87d1..ab2874f6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ pom.xml.asc *.swp .boot/tmp /boot +.nrepl-port +/build.boot diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..11ecb795 --- /dev/null +++ b/LICENSE @@ -0,0 +1,198 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' from + a Contributor if it was added to the Program by such Contributor itself or + anyone acting on such Contributor's behalf. Contributions do not include + additions to the Program which: (i) are separate modules of software + distributed in conjunction with the Program under their own license + agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly perform, + distribute and sublicense the Contribution of such Contributor, if any, and + such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of the + Contribution and the Program if, at the time the Contribution is added by + the Contributor, such addition of the Contribution causes such combination + to be covered by the Licensed Patents. The patent license shall not apply + to any other combinations which include the Contribution. No hardware per + se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses to + its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other entity + based on infringement of intellectual property rights or otherwise. As a + condition to exercising the rights and licenses granted hereunder, each + Recipient hereby assumes sole responsibility to secure any other + intellectual property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to distribute the Program, it + is Recipient's responsibility to acquire that license before distributing + the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under its +own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties and + conditions, express and implied, including warranties or conditions of + title and non-infringement, and implied warranties or conditions of + merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are offered + by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained within + the Program. + +Each Contributor must identify itself as the originator of its Contribution, if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, if +a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, damages +and costs (collectively "Losses") arising from claims, lawsuits and other legal +actions brought by a third party against the Indemnified Contributor to the +extent caused by the acts or omissions of such Commercial Contributor in +connection with its distribution of the Program in a commercial product +offering. The obligations in this section do not apply to any claims or Losses +relating to any actual or alleged intellectual property infringement. In order +to qualify, an Indemnified Contributor must: a) promptly notify the Commercial +Contributor in writing of such claim, and b) allow the Commercial Contributor to +control, and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may participate in +any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If that +Commercial Contributor then makes performance claims, or offers warranties +related to Product X, those performance claims and warranties are such +Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a court +requires any other Contributor to pay any damages as a result, the Commercial +Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using and +distributing the Program and assumes all risks associated with its exercise of +rights under this Agreement , including but not limited to the risks and costs +of program errors, compliance with applicable laws, damage to or loss of data, +programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS +GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under applicable +law, it shall not affect the validity or enforceability of the remainder of the +terms of this Agreement, and without further action by the parties hereto, such +provision shall be reformed to the minimum extent necessary to make such +provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted under +Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue and +survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to time. +No one other than the Agreement Steward has the right to modify this Agreement. +The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation +may assign the responsibility to serve as the Agreement Steward to a suitable +separate entity. Each new version of the Agreement will be given a +distinguishing version number. The Program (including Contributions) may always +be distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to distribute the Program (including its Contributions) +under the new version. Except as expressly stated in Sections 2(a) and 2(b) +above, Recipient receives no rights or licenses to the intellectual property of +any Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted under +this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. diff --git a/README.md b/README.md index b663647c..3e820f15 100644 --- a/README.md +++ b/README.md @@ -1,122 +1,523 @@ -# boot +![arhimedes lever][1] -Boot is a minimal Clojure program 'bootloader'. It reads a -configuration map from a `boot.edn` file in the current directory -and starts a JVM with Clojure, dependencies, and directories -on the classpath. After the JVM is set up it runs a function -or evaluates an expression as specified in the configuration. +# Boot -*This is experimental software and subject to frequent change.* - -![latest version][7] +Boot is a shell interpreter for scripts written in Clojure. It is designed to be +used with the “shebang” style of shell scripts to provide a simple means to have +single file, self-contained scripts in Clojure that can include dependencies and +so forth but don't need to be part of a project or uberjar. Also, boot is a +Clojure build tool. ## Overview -While boot can be used for just running some Clojure function in a -JVM, it can also be the foundation for better project build tooling. -The idea is: instead of a pseudo-declarative project.clj file in -your Clojure project, multiple JVMs, plugins, etc., you simply use -boot to run a Clojure function which builds your project. +Boot consists of two parts: the boot **loader** (this project), and the boot +**core**. + +* **The boot loader is a Clojure program in executable uberjar format.** The + purpose of the loader is to add the boot core dependency to the classpath + (fetching it from Clojars if necessary) and then hand off execution to the + `-main` function defined there. The loader is designed to be as small, simple, + and stable as possible, because updating to a new version is relatively + awkward and it's important that scripts be runnable even if they were written + for an older version of the API. + +* **The boot core is a Maven dependency containing all of the actual boot + logic.** Since it's loaded into the loader dynamically it can be updated + easily, without requiring changes to the loader. The core version is specified + in the script file to provide repeatability–scripts pull in everything they + need to run, including the boot core itself. In addition to the machinery for + interpreting Clojure scripts, the core also contains a number of features + specific to boot's other role as a build tool for Clojure projects. + +### Clojure Scripts + +Boot scripts have three parts: the **shebang**, the **core version declaration**, +and a number of (optional) **top level forms**. + +The shebang tells your shell to use the boot loader to interpret the script: + +```clojure +#!/usr/bin/env boot +``` + +The core version declaration tells the boot loader which version of the boot +core to use: + +```clojure +#tailrecursion.boot.core/version "2.0.0" +``` + +Any remaining forms in the script file are evaluated in the boot environment: + +```clojure +(set-env! :dependencies '[[riddley "0.3.4"]]) +(require '[clojure.string :refer [join]]) + +(defn -main [& args] + (println (join " " ["hello," "world!"])) + (System/exit 0)) +``` + +## Getting Started + +To build boot you will need: + +* Java 1.6+ +* [Leiningen][4] 2 +* GNU Make + +Build and install boot: + +``` +$ git clone git@github.com:tailrecursion/boot +$ cd boot +$ make boot +$ mv ./boot ~/bin/boot # or anywhere else in your $PATH +``` -### Configuration +### Hello World -Boot maintains its state in a configuration atom initially -derived from the data in the `boot.edn` file. The configuration -can be modified at runtime to manipulate the application state, -i.e. add dependencies to the classpath, etc. An additional global -configuration file `~/.boot.edn` in the user's home directory may -contain configuration data which is to be included in all builds. +A simple example to get started: -Options may be passed to the `java` command via the `JVM_OPTS` -environment variable. +```clojure +#!/usr/bin/env boot -### Middleware +#tailrecursion.boot.core/version "2.0.0" -Individual tasks within the build process are composed of middleware -(like [ring][1] does, for example). Everything is implemented as -Clojure functions, so it's easy to customize the build process for -individual projects and to package these build processes for -distribution. +(defn -main [& args] + (println "hello, world!") + (System/exit 0)) +``` -The task middleware is composed into an application at runtime -according to the command line options passed to boot. Individual -tasks may be passed arguments at this time, as well. +Write that to a file, say `build.boot`, set execute permissions on the file, and +run it in the terminal to enjoy a friendly greeting. -There is a selection of generally applicable middleware included in -the [boot.task][2] repository to do useful things like watch -directories for changed files, sync/copy files between directories, -etc. This is a good place to look for examples when building custom -tasks. +> Note: scripts interpreted by boot must have the `.boot` file extension. -### Tasks +### Script Dependencies -Boot tasks may also be specified as subconfigurations inside the -`boot.edn` file, and are similar to Leiningen's [profiles][3]. Multiple -subconfigurations may be specified. When invoked from the command line -the selected subconfiguration is merged into the current -configuration. Tasks can be used to call pre-packaged middleware -stacks, like a lightweight Leiningen plugin. +Scripts can add Maven repositories and/or dependencies at runtime using +`set-env!`: -### Files +```clojure +#!/usr/bin/env boot -Since most build processes generate files at some point, boot -includes facilities for creating and managing temporary files -and directories. This temporary filesystem facility can be used -to have "auto-cleaning" builds—builds that don't need to have -a "clean" target because they don't create stale garbage. +#tailrecursion.boot.core/version "2.0.0" -There is also facility for collecting output files at the end of -the boot process and overlaying and syncing them to the final -project output directories. This eliminates the possibility of -stale output files while preserving the ability of individual -tasks to produce or delete output files without clobbering each -other. +(set-env! + :repositories #{"http://me.com/maven-repo"} + :dependencies '[[com.hello/foo "0.1.0"]]) -## Install +(require '[com.hello.foo :as foo]) -To build and run boot your system must have: -* Java version 7+ -* [Leiningen][4] version 2+ -* GNU `make` +(defn -main [& args] + (println (foo/do-stuff args)) + (System/exit 0)) +``` -To build boot from source run the following commands in a terminal -in the boot repo directory (pre-made binaries will be made available -someday): +## Boot Build Tool - $ make boot - $ cp ./boot /somewhere/in/your/path/ +In addition to interpreting scripts, boot also provides some facilities to help +build Clojure projects. Omitting the `-main` function definition puts boot into +build tool mode. -## Usage +### A Minimal Build Script -Check out the [example boot.edn][5] in this project. It loads a Maven -dependency, adds a directory to the classpath, and specifies a -function to evaluate when the JVM is all set up. +Create a minimal `build.boot` file containing only the shebang and core version: - $ boot +``` +$ boot :strap > build.boot +``` -Tasks are invoked by passing the name of the task key as the -first command line argument. +The resulting file should contain something like this: - # invoke the (built-in) :help task - $ boot help +```clojure +#!/usr/bin/env boot -Multiple tasks can be invoked in a single build process, too. +#tailrecursion.boot.core/version "2.0.0" +``` - # invoke the :foo task and then the :bar task - $ boot foo bar +Then run it. You should see version and usage info and a list of available +tasks: -If the task takes arguments it can be invoked by enclosing the -task name and arguments in square brackets. +``` +$ boot +tailrecursion/boot 1.0.0: http://github.com/tailrecursion/boot - # invoke the (built-in) :help task with an argument - $ boot [help foo] +Usage: boot OPTS task ... + boot OPTS [task arg arg] ... + boot OPTS [help task] -Increase JVM heap space to 1G: +OPTS: :v Verbose exceptions (full cause trace). + [:v n] Cause trace limited to `n` elements each. - $ JVM_OPTS="-Xmx1g -server" boot [foo {:bar 42}] baf [quux foop] +Tasks: debug Print the value of a boot environment key. + help Print help and usage info for a task. + lein Run a leiningen task with a generated `project.clj`. + repl Launch nrepl in the project. + syncdir Copy/sync files between directories. + uberboot Build AOT compiled boot loader with all project deps included. + watch Watch `:src-paths` and call its continuation when files change. -Note that the command line options are read in as Clojure forms, so you can -pass a map as an argument to a task, for example, as can be seen above. +Create a minimal boot script: `boot :strap > build.boot` + +``` + +The tasks listed in the output are defined in the [core tasks namespace][5], +which is referred into the script namespace automatically. Any tasks defined or +referred into the script namespace will be displayed in the list of available +tasks printed by the default `help` task. + +> Notice that when the boot script file is named `build.boot` and located is in +> the current directory you can call `boot` directly instead of executing the +> boot script file itself. This is more familiar to users of Leiningen or GNU +> Make, for example, and reinforces build repeatability by standardizing the +> build script filename and location in the project directory. + +### A Simple Task + +Let's create a task to print a friendly greeting to the terminal. Modify the +`build.boot` file to contain the following: + +```clojure +#!/usr/bin/env boot + +#tailrecursion.boot.core/version "2.0.0" + +(deftask hello + "Print a friendly greeting." + [& [name]] + (fn [continue] + (fn [event] + (printf "hello, %s!\n" (or name "world")) + (continue event)))) +``` + +Run it again to see the new task listed among the other available tasks: + +``` +$ boot +tailrecursion/boot 1.0.0: http://github.com/tailrecursion/boot + +Usage: boot OPTS task ... + boot OPTS [task arg arg] ... + boot OPTS [help task] + +OPTS: :v Verbose exceptions (full cause trace). + [:v n] Cause trace limited to `n` elements each. + +Tasks: debug Print the value of a boot environment key. + hello Print a friendly greeting. + help Print help and usage info for a task. + lein Run a leiningen task with a generated `project.clj`. + repl Launch nrepl in the project. + syncdir Copy/sync files between directories. + uberboot Build AOT compiled boot loader with all project deps included. + watch Watch `:src-paths` and call its continuation when files change. + +Create a minimal boot script: `boot :strap > build.boot` + +``` + +Now we can run the `hello` task: + +``` +$ boot hello +hello, world! +``` + +### Command Line Arguments To Tasks + +An argument can be passed to the `hello` task like this: + +``` +$ boot \(hello :foo\) +hello, :foo! +``` + +The command line is read as Clojure forms, but task expressions can be enclosed +in square brackets (optionally) to avoid having to escape parens in the shell, +like this: + +``` +$ boot [hello :foo] +hello, :foo! +``` + +### Command Line Composition Of Tasks + +Tasks can be composed on the command line by specifying them one after the other, +like this: + +``` +$ boot [hello :foo] [hello :bar] +hello, :foo! +hello, :bar! +``` + +Because tasks return middleware functions they can be composed uniformly, and +the product of the composition of two task middleware functions is itself a +task middleware function. The two instances of the `hello` task above are being +combined by boot something like this: + +```clojure +;; [& args] command line argument list +("[hello" ":foo]" "[hello" ":bar]") + ;; string/join with " " and read-string + => ([hello :foo] [hello :bar]) + ;; convert top-level vectors to lists + => ((hello :foo) (hello :bar)) + ;; compose with comp when more than one + => (comp (hello :foo) (hello :bar)) +``` + +This yields a middleware function that is called by boot to actually perform +the build process. The composition of middleware sets up the pipeline of tasks +that will participate in the build. The actual handler at the bottom of the +middleware stack is provided by Boot–it syncs artifacts between temporary +staging directories (more on these later) and output/target directories. + +### Create New Task By Composition + +Here we create a new named task in the project boot script by composing other +tasks. This is a quick way to fix options and simplify documenting the build +procedures. Tasks are functions that return middleware, and middleware are +functions that can be composed uniformly, so a task can compose other tasks the +same way as on the command line: with the `comp` function. + +Modify the `build.boot` file such that it contains the following: + +```clojure +#!/usr/bin/env boot + +#tailrecursion.boot.core/version "2.0.0" + +(deftask hello + "Print a friendly greeting." + [& [name]] + (fn [continue] + (fn [event] + (printf "hello, %s!\n" (or name "world")) + (continue event)))) + +(deftask hellos + "Print two friendly greetings." + [] + (comp (hello :foo) (hello :bar))) +``` + +Now run the new `hellos` task, which composes two instances of the `hello` task +with different arguments to the constructor: + +``` +$ boot hellos +hello, :foo! +hello, :bar! +``` + +### The Build Environment + +The global build environment contains the project metadata. This includes things +like the project group and artifact ID, version string, dependencies, etc. The +environment is accessible throughout the build process via the `get-env` and +`set-env!` functions. + +For example: + +```clojure +#!/usr/bin/env boot + +#tailrecursion.boot.core/version "2.0.0" + +(set-env! + :project 'com.me/my-project + :version "0.1.0-SNAPSHOT" + :description "My Clojure project." + :url "http://me.com/projects/my-project" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :dependencies '[[tailrecursion/boot.task "2.0.0"] + [tailrecursion/hoplon "5.0.0"]] + :src-paths #{"src"}) + +(deftask env-value + "Print the value associated with `key` in the build environment." + [key] + (fn [continue] + (fn [event] + (prn (get-env key)) + (continue event)))) +``` + +In the example above the environment is configured using `set-env!` and a task +is defined to print the environment value associated with a given key using +`get-env`. (This task is similar to the core `debug` task that is included in +boot already.) We can run the task like this: + +``` +$ boot [env-value :src-paths] +#{"src"} +``` + +### Tasks That Modify The Environment + +Tasks defined in the `build.boot` script can dynamically modify the build +environment at runtime. That is, they can use `set-env!` to add dependencies or +directories to the classpath or otherwise update values in the build +environment. This makes it possible to define "profile" tasks that can be used +to modify the behavior of other tasks. These profile-type tasks can either +create a middleware function or simply return Clojure's `identity` to pass +control directly to the next task. + +For example: + +```clojure +#!/usr/bin/env boot + +#tailrecursion.boot.core/version "2.0.0" + +(set-env! + :project 'com.me/my-project + :version "0.1.0-SNAPSHOT" + :description "My Clojure project." + :url "http://me.com/projects/my-project" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :dependencies '[[tailrecursion/boot.task "2.0.0"] + [tailrecursion/hoplon "5.0.0"]] + :src-paths #{"src"}) + +(deftask env-mod + "Example profile-type task." + [] + (set-env! :description "My TEST Clojure project.") + identity) + +(deftask env-value + "Print the value associated with `key` in the build environment." + [key] + (fn [continue] + (fn [event] + (prn (get-env key)) + (continue event)))) +``` + +Now, running this `build.boot` script produces the following: + +``` +$ boot [env-value :description] +"My Clojure project." +$ boot env-mod [env-value :description] +"My TEST Clojure project." +``` + +In the build script the `deftask` macro defines a function whose body is +compiled lazily at runtime when the function is called. This means that inside +a `deftask` you can add dependencies and require namespaces which will then +be available for use in the build script. + +For example: + +```clojure +#!/usr/bin/env boot + +#tailrecursion.boot.core/version "2.0.0" + +(set-env! + :project 'com.me/my-project + :version "0.1.0-SNAPSHOT" + :description "My Clojure project." + :url "http://me.com/projects/my-project" + :license {:name "Eclipse Public License" + :url "http://www.eclipse.org/legal/epl-v10.html"} + :src-paths #{"src"}) + +(deftask load-hoplon + "Example profile-type task." + [] + (set-env! + :dependencies '[[tailrecursion/boot.task "2.0.0"] + [tailrecursion/hoplon "5.0.0"]]) + (require '[tailrecursion.hoplon.boot :as h]) + identity) +``` + +The `load-hoplon` task adds the dependencies needed for building a Hoplon +application and requires the hoplon boot task namespace, aliasing it to `h` +locally. To see the effect run the `build.boot` script with and without this +task and see how the list of available tasks changes. + +First without the `load-hoplon` profile: + +``` +$ boot help +tailrecursion/boot 1.0.0: http://github.com/tailrecursion/boot + +Usage: boot OPTS task ... + boot OPTS [task arg arg] ... + boot OPTS [help task] + +OPTS: :v Verbose exceptions (full cause trace). + [:v n] Cause trace limited to `n` elements each. + +Tasks: debug Print the value of a boot environment key. + help Print help and usage info for a task. + lein Run a leiningen task with a generated `project.clj`. + load-hoplon Example profile-type task. + repl Launch nrepl in the project. + syncdir Copy/sync files between directories. + uberboot Build AOT compiled boot loader with all project deps included. + watch Watch `:src-paths` and call its continuation when files change. + +Create a minimal boot script: `boot :strap > build.boot` + +``` + +Then with the `load-hoplon` profile: + +``` +$ boot load-hoplon help +tailrecursion/boot 1.0.0: http://github.com/tailrecursion/boot + +Usage: boot OPTS task ... + boot OPTS [task arg arg] ... + boot OPTS [help task] + +OPTS: :v Verbose exceptions (full cause trace). + [:v n] Cause trace limited to `n` elements each. + +Tasks: debug Print the value of a boot environment key. + help Print help and usage info for a task. + lein Run a leiningen task with a generated `project.clj`. + load-hoplon Example profile-type task. + repl Launch nrepl in the project. + syncdir Copy/sync files between directories. + uberboot Build AOT compiled boot loader with all project deps included. + watch Watch `:src-paths` and call its continuation when files change. + h/hoplon Build Hoplon web application. + h/html2cljs Convert file from html syntax to cljs syntax. + +Create a minimal boot script: `boot :strap > build.boot` + +``` + +Notice how the second list includes `h/hoplon` and `h/html2cljs`, the two tasks +defined using `deftask` in the [Hoplon boot task namespace][6]. You could run +the `hoplon` task, for example, by doing + +``` +$ boot load-hoplon h/hoplon +``` + +### Staging Directories And Temporary Files + +FIXME: content here + +## Artifacts + +Artifacts are published on Clojars. + +[![latest version][2]][3] ## License @@ -124,10 +525,14 @@ Copyright © 2013 Alan Dipert and Micha Niskin Distributed under the Eclipse Public License, the same as Clojure. -[1]: https://github.com/mmcgrana/ring -[2]: https://github.com/tailrecursion/boot.task -[3]: https://github.com/technomancy/leiningen/blob/master/doc/PROFILES.md +[1]: https://raw.github.com/tailrecursion/boot/master/img/archimedes-lever.gif +[2]: https://clojars.org/tailrecursion/boot/latest-version.svg +[3]: https://clojars.org/tailrecursion/boot [4]: https://github.com/technomancy/leiningen -[5]: https://github.com/tailrecursion/boot/blob/master/boot.edn -[6]: https://clojars.org/tailrecursion/boot -[7]: https://clojars.org/tailrecursion/boot/latest-version.svg +[5]: https://github.com/tailrecursion/boot.core/blob/master/src/tailrecursion/boot/core/task.clj +[6]: https://github.com/tailrecursion/hoplon/blob/master/src/tailrecursion/hoplon/boot.clj + +[10]: https://github.com/mmcgrana/ring +[20]: https://github.com/tailrecursion/boot.task +[30]: https://github.com/technomancy/leiningen/blob/master/doc/PROFILES.md +[50]: https://github.com/tailrecursion/boot/blob/master/boot.edn diff --git a/src/data_readers.clj b/src/data_readers.clj new file mode 100644 index 00000000..3d4a73fc --- /dev/null +++ b/src/data_readers.clj @@ -0,0 +1 @@ +{tailrecursion.boot.core/version tailrecursion.boot.loader/install-core} diff --git a/src/tailrecursion/boot/loader.clj b/src/tailrecursion/boot/loader.clj index 0109ad80..776dc3a7 100644 --- a/src/tailrecursion/boot/loader.clj +++ b/src/tailrecursion/boot/loader.clj @@ -8,14 +8,22 @@ (ns tailrecursion.boot.loader (:require - [clojure.java.io :as io] - [clojure.string :as string] - [cemerick.pomegranate :as pom]) + [clojure.java.io :as io] + [clojure.string :as string] + [clojure.pprint :as pprint] + [cemerick.pomegranate :as pom] + [clojure.stacktrace :as trace] + [tailrecursion.boot.strap :as strap]) (:gen-class)) (defmacro guard [expr & [default]] `(try ~expr (catch Throwable _# ~default))) +(defn auto-flush + [writer] + (proxy [java.io.PrintWriter] [writer] + (write [s] (.write writer s) (flush)))) + (defn info "Returns a map of version information for tailrecursion.boot.loader" [] @@ -31,14 +39,15 @@ (assoc coordinate (inc idx) (into exclusions syms))) (into coordinate [:exclusions syms]))) +(def exclude-clj (partial exclude ['org.clojure/clojure])) + (defn transfer-listener [{type :type meth :method {name :name repo :repository} :resource err :error}] (when (.endsWith name ".jar") (case type :started (printf "Retrieving %s from %s\n" name repo) (:corrupted :failed) (when err (printf "Error: %s\n" (.getMessage err))) - nil) - (flush))) + nil))) (defn ^:from-leiningen build-url "Creates java.net.URL from string" @@ -68,19 +77,92 @@ :password password :non-proxy-hosts (get-non-proxy-hosts)})))) -(defn add-dependencies! [deps repos] - (let [deps (mapv (partial exclude ['org.clojure/clojure]) deps)] - (pom/add-dependencies :coordinates deps - :repositories (zipmap repos repos) - :transfer-listener transfer-listener - :proxy (get-proxy-settings)))) - -(defn -main [core-version arg0 & args] - (add-dependencies! - [['tailrecursion/boot.core core-version]] - #{"http://repo1.maven.org/maven2/" "http://clojars.org/repo/"}) - (require 'tailrecursion.boot) - (let [main (find-var (symbol "tailrecursion.boot" "-main"))] - (try (apply main (info) arg0 args) - (catch Throwable e (.printStackTrace e) (System/exit 1))) - (System/exit 0))) +(def core-dep (atom nil)) +(def dfl-repos #{"http://clojars.org/repo/" "http://repo1.maven.org/maven2/"}) + +(defn add-dependencies! + ([deps] (add-dependencies! deps dfl-repos)) + ([deps repos] + (let [deps (mapv exclude-clj deps)] + (pom/add-dependencies :coordinates deps + :repositories (zipmap repos repos) + :transfer-listener transfer-listener + :proxy (get-proxy-settings))))) + +(defrecord CoreVersion [depspec]) + +(defn print-core-version + ([this] + (let [[[_ version & _]] (:depspec this)] + (printf "#tailrecursion.boot.core/version %s" (pr-str version)))) + ([this w] + (let [[[_ version & _]] (:depspec this)] + (.write w "#tailrecursion.boot.core/version ") + (.write w (pr-str version))))) + +(defmethod print-method CoreVersion [this w] + (print-core-version this w)) + +(. clojure.pprint/simple-dispatch addMethod CoreVersion print-core-version) +(. clojure.pprint/code-dispatch addMethod CoreVersion print-core-version) + +(defn install-core [version] + (locking core-dep + (let [core (->CoreVersion [(exclude-clj ['tailrecursion/boot.core version])])] + (if @core-dep core (doto core ((partial reset! core-dep))))))) + +(defmacro with-rethrow [expr msg] + `(try ~expr (catch Throwable e# (throw (Exception. ~msg e#))))) + +(defmacro with-err [& body] + `(binding [*out* *err*] ~@body (System/exit 1))) + +(defmacro with-terminate [expr] + `(try + ~expr + (System/exit 0) + (catch Throwable e# + (with-err (trace/print-cause-trace e#))))) + +(defn usage [] + (print + (str + "Usage: boot :strap\n" + " boot [arg ...]\n" + " boot [arg ...]\n\n" + "Create a minimal boot script: `boot :strap > build.boot`\n\n"))) + +(defn wrong-ext [arg0] + (printf "boot: script file doesn't have .boot extension: %s\n\n" arg0)) + +(defn script-not-found [arg0 badext] + (print + (str + (format "boot: script file not found: %s\n" arg0) + (if-not badext + "\n" + (format "Perhaps %s should have the .boot extension?\n\n" badext))))) + +(defn no-core-dep [arg0] + (printf "boot: no boot.core dependency specified in %s\n\n" arg0)) + +(defn -main [& [arg0 & args :as args*]] + (binding [*out* (auto-flush *out*) + *err* (auto-flush *err*)] + (with-terminate + (case arg0 + ":strap" (strap/emit add-dependencies!) + (let [file? #(and % (.isFile (io/file %))) + dotboot? #(and % (.endsWith (.getName (io/file %)) ".boot")) + script? #(and % (dotboot? %)) + badext (and (file? arg0) (not (script? arg0)) arg0) + [arg0 args] (if (script? arg0) [arg0 args] ["build.boot" args*])] + (when-not (file? arg0) (with-err (script-not-found arg0 badext) (usage))) + (let [script (->> arg0 slurp (format "(%s)") read-string)] + (if-let [core-dep* (:depspec @core-dep)] + (do (add-dependencies! core-dep*) + (require 'tailrecursion.boot) + (let [main (find-var (symbol "tailrecursion.boot" "-main")) + info (update-in (info) [:dependencies] into core-dep*)] + (apply main info arg0 script args))) + (with-err (no-core-dep arg0) (usage))))))))) diff --git a/src/tailrecursion/boot/strap.clj b/src/tailrecursion/boot/strap.clj new file mode 100644 index 00000000..012dfcd2 --- /dev/null +++ b/src/tailrecursion/boot/strap.clj @@ -0,0 +1,12 @@ +(ns tailrecursion.boot.strap) + +(defn- latest-core-version [add-deps!] + (add-deps! '[[tailrecursion/ancient-clj "0.1.7"]]) + (require 'ancient-clj.core) + (let [latest-version-string! (resolve 'ancient-clj.core/latest-version-string!)] + (latest-version-string! {:snapshots? false} 'tailrecursion/boot.core))) + +(defn emit [add-deps!] + (printf + "#!/usr/bin/env boot\n\n#tailrecursion.boot.core/version %s\n" + (pr-str (latest-core-version add-deps!))))