Tutorial 20 - House Keeping
March 7, 2017 · View on GitHub
In the previous tutorial we explained how to make a
library compliant with
Reader Conditionals. As
an example, we exploited the
valip library that we already
used in the modern-cljs project for validating the input fields of
the Login and Shopping forms we adopted as CLJ/CLJS playground.
Even if we reached a decent result, we left some work to be done, namely the deployment of the updated library to clojars, the notorious community repository for open source Clojure libraries.
Preamble
Unless you completed the previous tutorial and finally committed
your work in the reader-conditionals branch of the valip project
you created while following the tutorial itself, to be able to work on
this new tutorial, you need to do as follows:
git clone https://github.com/magomimmo/valip.git
cd valip
git checkout se-tutorial-19
git checkout -b reader-conditionals
Introduction
In this tutorial we're going to fill that gap. But first we have to
digress on a couple of topics we left uncovered about the boot build
tool, because they constitute a prerequisite to the clojars
deployment itself, namely:
- POM (Project Object Model)
- Dependency Scope
Minimal POM (Project Object Model)
To quickly finish the
previous tutorial,
we switched to leiningen and locally installed the updated version
of valip by using the lein install task. This was because the
project.clj build file used by leiningen already had the
minimal information
needed to create and package the valip library, while we had not
updated the corresponding build.boot build file used by boot with
the same information, namely:
-
the
groupId:it has to follow the java package name rules; e.g.,org.clojars.magomimmo; -
the
artifactId: it is the name of thejarfile without version; e.g.,valip; -
the
version: it is suggested to follow the Semantic Version Specification; e.g., "0.4.0-SNAPSHOT"NOTE 1:
-SNAPSHOTqualifies a version "as-yet-unreleased". Under the hood,maven, on which bothleiningenandbootare based, will fetch the most recently deployed-SNAPSHOTversion. Even if this behavior slows down the build process, in a continuous integration scenario it guarantees up-to-date builds, while minimizing the amount of rebuilding that is required for each integration step.
In boot, to create a project's POM, package it in a jar and
finally install it in the local maven repository on your machine,
you can chain the following three
built-in
boot tasks:
pomjarinstall
The pom task
Let's look at the pom docstring first:
cd /path/to/valip
boot pom -h
Create project pom.xml file.
The project and version must be specified to make a pom.xml.
Options:
-h, --help Print this help info.
-p, --project SYM Set the project id (eg. foo/bar) to SYM.
-v, --version VER Set the project version to VER.
-d, --description DESC Set the project description to DESC.
-u, --url URL Set the project homepage url to URL.
-s, --scm KEY=VAL Conj [KEY VAL] onto the project scm map (KEY is one of url, tag, connection, developerConnection).
-l, --license NAME:URL Conj [NAME URL] onto the map {name url} of project licenses.
-o, --developers NAME:EMAIL Conj [NAME EMAIL] onto the map {name email} of project developers.
-D, --dependencies SYM:VER Conj [SYM VER] onto the project dependencies vector (overrides boot env dependencies).
As you see, to create a minimal pom.xml file for a project you need
to specify at least the -p and the -v command line options. Before
testing the pom task, let's first get rid of the stuff generated in
the
previous tutorial,
when we ran the lein install command:
# delete files generated in the project directory
cd /path/to/valip
lein clean
rm -rf pom.xml
# delete the jar file from the local maven repository
rm -rf ~/.m2/repository/org/clojars/magomimmo/valip
Now launch the pom task at the command line, by providing the minimal
information needed to generate the pom.xml file:
boot pom -p org.clojars.magomimmo/valip -v 0.4.0-SNAPSHOT
Writing pom.xml and pom.properties...
NOTE 2: remember to substitute
magomimmowith your github name asgroupId
Even if the command reported the writing of the pom.xml and the
pom.properties files, which is a misleading message to me, if you
take a look at the project directory you won't see them:
tree
.
├── README.md
├── boot.properties
├── build.boot
├── project.clj
├── src
│ └── valip
│ ├── core.cljc
│ ├── macros.clj
│ └── predicates.cljc
└── test
└── valip
└── test
├── core.cljc
└── predicates.cljc
NOTE 3: after I wrote the previous tutorial, I decided to move the
def.cljfile containing thevalipmacros to a more conventional place and rename it asmacros.clj. You could do the same thing as a very simple exercise.
This is because we did not chain the target task after the pom
one. Before the 2.5.5. release, boot did not have a target task.
The default behavior was to automatically write to an implicit
target directory, which is not the case anymore when you set, as we
did in the
previous tutorial,
the BOOT_EMIT_TARGET environment to no in the boot.properties
file of the valip project.
The show task
In boot, the local installation process of a project doesn't need to
create a target directory in your project directory to succeed. If you
want to see what is going on in the boot's
fileset before and
after the execution of any boot task, you can use the show
built-in task:
Let's view the docstring for the show task:
boot show -h
Print project/build info (e.g. dependency graph, etc).
Options:
-h, --help Print this help info.
-C, --fake-classpath Print the project's fake classpath.
-c, --classpath Print the project's full classpath.
-d, --deps Print project dependency graph.
-e, --env Print the boot env map.
-f, --fileset Print the build fileset object.
-l, --list-pods Print the names of all active pods.
-p, --pedantic Print graph of dependency conflicts.
-P, --pods REGEX Set the name filter used to select which pods to inspect to REGEX.
-U, --update-snapshots Include snapshot versions in updates searches.
-u, --updates Print newer releases of outdated dependencies.
Wow, this is a very handy task indeed. Let's play on the
command line by chaining it before and after the pom task as
follows:
boot show -f pom -p org.clojars.magomimmo/valip -v 0.4.0-SNAPSHOT show -f
└── valip
├── core.cljc
├── macros.clj
└── predicates.cljc
Writing pom.xml and pom.properties...
├── valip
│ ├── core.cljc
│ ├── macros.clj
│ └── predicates.cljc
└── META-INF
└── maven
└── org.clojars.magomimmo
└── valip
├── pom.properties
└── pom.xmlc
Initially, the fileset includes the valip source code files
only. Then, the pom task adds the META-INF directory containing
what later will be needed to package the project into a jar file.
The jar task
Let's now view the jar docstring:
boot jar -h
Build a jar file for the project.
Options:
-h, --help Print this help info.
-f, --file PATH Set the target jar file name to PATH.
-M, --manifest KEY=VAL Conj [KEY VAL] onto the jar manifest map.
-m, --main MAIN Set the namespace containing the -main function to MAIN.
At the moment, we're not interested in any of its command line
options. The jar's help does not tell you, but if you do not specify
a file name with the -f option, the jar task will
concatenate
the project's artifactId and version as
default name,
which is what we want.
Try it:
boot pom -p org.clojars.magomimmo/valip -v 0.4.0-SNAPSHOT jar show -f
Writing pom.xml and pom.properties...
Writing valip-0.4.0-SNAPSHOT.jar...
├── valip
│ ├── core.cljc
│ ├── macros.clj
│ └── predicates.cljc
├── META-INF
│ └── maven
│ └── org.clojars.magomimmo
│ └── valip
│ ├── pom.properties
│ └── pom.xml
└── valip-0.4.0-SNAPSHOT.jar
Do you see the valip-0.4.0-SNAPSHOT.jar file shown by the show -f
task?
The install task
We are almost done. install is the next built-in task to be chained
for installing the valip library in the maven local
repository. Let's view its docstring:
boot install -h
Install project jar to local Maven repository.
The --file option allows installation of arbitrary jar files. If no
file option is given then any jar artifacts created during the build
will be installed.
The pom.xml file that's required when installing a jar can usually be
found in the jar itself. However, sometimes a jar might contain more
than one pom.xml file or may not contain one at all.
The --pom option can be used in these situations to specify which
pom.xml file to use. The optarg denotes either the path to a pom.xml
file in the filesystem or a subdir of the META-INF/maven/ dir in which
the pom.xml contained in the jar resides.
Example:
Given a jar file (warp-0.1.0.jar) with the following contents:
.
├── META-INF
│ ├── MANIFEST.MF
│ └── maven
│ └── tailrecursion
│ └── warp
│ ├── pom.properties
│ └── pom.xml
└── tailrecursion
└── warp.clj
The jar could be installed with the following boot command:
$ boot install -f warp-0.1.0.jar -p tailrecursion/warp
Options:
-h, --help Print this help info.
-f, --file PATH Set the jar file to install to PATH.
-p, --pom PATH Set the pom.xml file to use to PATH.
This help is mostly about corner cases which are not in our
scenario. We'll stay with the default behavior, without passing any
options to the install task:
boot pom -p org.clojars.<your_github_name>/valip -v 0.4.0-SNAPSHOT jar install
Writing pom.xml and pom.properties...
Writing valip-0.4.0-SNAPSHOT.jar...
Installing valip-0.4.0-SNAPSHOT.jar...
Now take a look at your local maven repository:
tree ~/.m2/repository/org/clojars/<your_github_name>/valip
├── 0.4.0-SNAPSHOT
│ ├── _maven.repositories
│ ├── maven-metadata-local.xml
│ ├── valip-0.4.0-SNAPSHOT.jar
│ └── valip-0.4.0-SNAPSHOT.pom
└── maven-metadata-local.xml
It is worth nothing that the valip directory is still clean, because
we did not chain any target task:
tree
.
├── README.md
├── boot.properties
├── build.boot
├── project.clj
├── src
│ └── valip
│ ├── core.cljc
│ ├── macros.clj
│ └── predicates.cljc
└── test
└── valip
└── test
├── core.cljc
└── predicates.cljc
Task Options
The previous paragraphs explained the local installation of valip
step by step, but it is not something you'd like to repeat again and
again after any project change.
From a build tool such as boot you would expect something like the lein install command we used in the previous tutorial: enter
task-options!.
task-options! allows us to add any task option to the build.boot
build file. If you're only using built-in tasks, you can place it
just after the set-env! form. If you are using other tasks, you'll
place task-options! after the requirement form. Following is the
complete updated version of the build.boot build file for the
valip project:
(set-env!
:source-paths #{"src"}
:dependencies '[[org.clojure/clojure "1.8.0"]
[adzerk/boot-test "1.2.0"]
[org.clojure/clojurescript "1.9.494"]
[adzerk/boot-cljs "1.7.228-2"]
[crisptrutski/boot-cljs-test "0.3.0"]
[doo "0.1.7"]]
)
(require '[adzerk.boot-test :refer [test]]
'[adzerk.boot-cljs :refer [cljs]]
'[crisptrutski.boot-cljs-test :refer [test-cljs]])
(task-options!
pom {:project 'org.clojars.magomimmo/valip
:version "0.4.0-SNAPSHOT"
:description "Functional validation library for Clojure and ClojureScript.
Forked from https://github.com/cemerick/valip"
:url "http://github.com/magomimmo/valip"
:scm {:url "http://github.com/magomimmo/valip"}
:license {"Eclipse Public License" "http://www.eclipse.org/legal/epl-v10.html"}}
test {:namespaces #{'valip.test.core 'valip.test.predicates}}
test-cljs {:namespaces #{'valip.test.core 'valip.test.predicates}})
(deftask testing
[]
(merge-env! :source-paths #{"test"})
identity)
(deftask tdd
"Launch a CLJ TDD Environment"
[]
(comp
(testing)
(watch)
(test-cljs)
(test)))
NOTE 4: we moved the
testand thetest-cljsoption arguments into thetask-options!form as well.
NOTE 5: we enriched the
pomtask with more information, including optional fields like:description,:url,:scmand:licensethat are not part of a minimal POM required for the project to be packaged and installed.
Shoot the gun
We can now run the tdd task to verify that everything is still
working:
boot tdd
Compiling ClojureScript...
• cljs_test/generated_test_suite.js
;; ======================================================================
;; Testing with Phantom:
Testing valip.test.core
Testing valip.test.predicates
Ran 20 tests containing 86 assertions.
0 failures, 0 errors.
Testing valip.test.core
Testing valip.test.predicates
Ran 21 tests containing 90 assertions.
0 failures, 0 errors.
Elapsed time: 15.959 sec
It worked. Now stop the running boot process and try to reinstall
valip in the local maven repository by chaining the three cited
tasks as follows:
boot pom jar install
Writing pom.xml and pom.properties...
Writing valip-0.4.0-SNAPSHOT.jar...
Installing valip-0.4.0-SNAPSHOT.jar...
Verify that the installation process succeeded:
tree ~/.m2/repository/org/clojars/<your_github_name/valip/
├── 0.4.0-SNAPSHOT
│ ├── _maven.repositories
│ ├── maven-metadata-local.xml
│ ├── valip-0.4.0-SNAPSHOT.jar
│ └── valip-0.4.0-SNAPSHOT.pom
└── maven-metadata-local.xml
1 directory, 5 files
Nice job, but if you now think we're done, you're wrong! At least,
we have to test the local installation of the valip library in the
context of a project, as we already did in the
previous tutorial.
Test valip in a project context
First, if not already done, clone the modern-cljs project in a
temporary directory and checkout the se-tutorial-18 branch as
follows:
cd /path/to/tmp
git clone https://github.com/magomimmo/modern-cljs.git
cd modern-cljs
git checkout se-tutorial-18
Then edit its build.boot file by substituting the valip dependency
with the newly updated one:
(set-env!
...
:dependencies '[
...
[org.clojars.<your_github_name>/valip "0.4.0-SNAPSHOT"]
...
])
Next, start the TDD environment as usual:
boot tdd
Starting reload server on ws://localhost:60606
Writing boot_reload.cljs...
Writing boot_cljs_repl.cljs...
2016-01-10 14:12:42.402:INFO::clojure-agent-send-off-pool-0: Logging initialized @10813ms
clojure.lang.ExceptionInfo: java.io.FileNotFoundException: Could not locate valip/core__init.class or valip/core.clj on classpath., compiling:(modern_cljs/login/validators.cljc:1:1)
data: {:file
"/var/folders/17/1jg3ghkx73q4jtgw4z500www0000gp/T/boot.user2098590056263435211.clj",
:line 31}
java.util.concurrent.ExecutionException: java.io.FileNotFoundException: Could not locate valip/core__init.class or valip/core.clj on classpath., compiling:(modern_cljs/login/validators.cljc:1:1)
clojure.lang.Compiler$CompilerException: java.io.FileNotFoundException: Could not locate valip/core__init.class or valip/core.clj on classpath., compiling:(modern_cljs/login/validators.cljc:1:1)
java.io.FileNotFoundException: Could not locate valip/core__init.class or valip/core.clj on classpath.
...
Elapsed time: 26.573 sec
Argh. This is not so lovely! What's is happening here? It seems that
the boot tdd task is not able to find the source files of the
valip library in the classpath.
Previously, we should have taken a look at the content of the
generated jar file installed by boot in the local repository,
instead of quickly proceeding with the valip installation:
jar -tvf ~/.m2/repository/org/clojars/magomimmo/valip/0.4.0-SNAPSHOT/valip-0.4.0-SNAPSHOT.jar
0 Mon Mar 06 18:01:43 CET 2017 META-INF/
0 Mon Mar 06 18:01:43 CET 2017 META-INF/maven/
0 Mon Mar 06 18:01:43 CET 2017 META-INF/maven/org.clojars.magomimmo/
0 Mon Mar 06 18:01:43 CET 2017 META-INF/maven/org.clojars.magomimmo/valip/
2136 Mon Mar 06 18:01:43 CET 2017 META-INF/maven/org.clojars.magomimmo/valip/pom.xml
158 Mon Mar 06 18:01:43 CET 2017 META-INF/maven/org.clojars.magomimmo/valip/pom.properties
25 Mon Mar 06 18:01:43 CET 2017 META-INF/MANIFEST.MF
That's very bad. The locally installed jar package for the valip
library does not contain any valip source files. How this could
happen? The answer, my friend, is blowin' in the wind, namely in the
boot
wiki:
:resource-paths: A set of path strings. These paths will be on the classpath and the files contained will be emitted as final artifacts;:source-paths: A set of path strings. These paths will be on the classpath but the files contained will not be emitted as final artifacts.
Ahah! Even if the :source-paths set of directories will be on the
classpath of the project, as we already knew from the fact that
valip was able to be compiled and tested, the contained source files
would never be emitted in the final artifacts, as we just discovered
by listing the content of the generated jar file.
On the contrary, the files contained in the set of directories of the
:resource-paths would be emitted in the final jar file.
install-jar task
Now, go back to valip's build.boot build file. By just setting
the :resource-paths environment variable with the same #{"src"}
value of the :source-paths one, we should be able to solve the
problem of including valip's source files into the jar. While
we are at it, let's define a new install-jar task, which first sets
the :resource-paths environment variable and then composes the
pom, jar and install built-in tasks:
;; append at the end of `build.boot`
(deftask install-jar
[]
(merge-env! :resource-paths #{"src"})
(comp
(pom)
(jar)
(install)))
Let's see if it works:
cd /path/to/valip
boot install-jar
Writing pom.xml and pom.properties...
Writing valip-0.4.0-SNAPSHOT.jar...
Installing valip-0.4.0-SNAPSHOT.jar...
Now list the content of the generated jar package file again
jar -tvf ~/.m2/repository/org/clojars/magomimmo/valip/0.4.0-SNAPSHOT/valip-0.4.0-SNAPSHOT.jar
0 Mon Mar 06 18:09:25 CET 2017 META-INF/
0 Mon Mar 06 18:09:25 CET 2017 META-INF/maven/
0 Mon Mar 06 18:09:25 CET 2017 META-INF/maven/org.clojars.magomimmo/
0 Mon Mar 06 18:09:25 CET 2017 META-INF/maven/org.clojars.magomimmo/valip/
2136 Mon Mar 06 18:09:25 CET 2017 META-INF/maven/org.clojars.magomimmo/valip/pom.xml
158 Mon Mar 06 18:09:25 CET 2017 META-INF/maven/org.clojars.magomimmo/valip/pom.properties
0 Mon Mar 06 18:09:25 CET 2017 valip/
4961 Mon Mar 06 16:47:54 CET 2017 valip/predicates.cljc
1027 Mon Mar 06 16:47:31 CET 2017 valip/macros.clj
995 Tue Feb 28 18:17:17 CET 2017 valip/core.cljc
25 Mon Mar 06 18:09:25 CET 2017 META-INF/MANIFEST.MF
That's much better. Run the modern-cljs again project to see the
result:
cd /path/to/modern-cljs
boot tdd
Starting reload server on ws://localhost:46523
Writing boot_cljs_repl.cljs...
2017-03-06 18:15:26.943:INFO::clojure-agent-send-off-pool-0: Logging initialized @21591ms
2017-03-06 18:15:27.073:INFO:oejs.Server:clojure-agent-send-off-pool-0: jetty-9.2.10.v20150310
2017-03-06 18:15:27.137:INFO:oejs.ServerConnector:clojure-agent-send-off-pool-0: Started ServerConnector@2a6e421a{HTTP/1.
1}{0.0.0.0:3000}
2017-03-06 18:15:27.138:INFO:oejs.Server:clojure-agent-send-off-pool-0: Started @21786ms
Started Jetty on http://localhost:3000
Starting file watcher (CTRL-C to quit)...
Writing adzerk/boot_reload.cljs to connect to ws://localhost:46523...
nREPL server started on port 38395 on host 127.0.0.1 - nrepl://127.0.0.1:38395
Writing clj_test/suite.cljs...
Writing main.cljs.edn...
Compiling ClojureScript...
WARNING: Replacing ClojureScript compiler option :main with automatically set value.
• main.js
Running cljs tests...
Testing modern-cljs.login.validators-test
Testing modern-cljs.shopping.validators-test
Ran 3 tests containing 55 assertions.
0 failures, 0 errors.
Testing modern-cljs.login.validators-test
Testing modern-cljs.shopping.validators-test
Ran 4 tests containing 56 assertions.
0 failures, 0 errors.
Writing target dir(s)...
Elapsed time: 34.338 sec
Now we are talking. Just to be sure that everything is still working as at the end of the Tutorial 18, visit the Shopping Calculator URL to play with the Shopping Calculator.
You can now stop the boot process and go back again to the valip
project directory, because there is something new to learn before
deploying the valip library to clojars and making it available to
anybody else.
Artifact Versioning
When we made the valip library compliant with the Reader
Conditionals extension, we changed its version identifier from
"0.3.2" to "0.4.0-SNAPSHOT". The reason why there are so many
0.x.y versioned libraries around, it's because until their APIs get
stable, they never get versioned with a major version number. In the
mean time, as dictated by the
The Semantic Version Specification, anything
could change. By considering that a lot of open source libraries stay
as unstable for a long time, their minor version number is generally
treated as major version number, meaning that minor version number
increments do not guarantee any backward compatibility. This is why we
incremented the minor version only, and not the major version as well,
even if our valip version is not backward compatible with the
0.3.2
What's next
Let's first summarize what we already did with valip:
- we forked and cloned the
valiplibrary by Chas Emerick; - we added the remote upstream repo;
- we created the
reader-conditionalsbranch; - we modified the leiningen
project.cljbuild file to update the CLJ dependency from1.4.0to1.8.0; - we made substantial changes to the
valipsource and test files to make it compatible with the Reader Conditionals extension of CLJ/CLJS compilers and to introduce few corner cases tests; - we incremented the minor-version only of the library, because, even if
it would break any preexisting use of the
valiplibrary, its major version is still0; - we qualified the new minor version as SNAPSHOT, because it's as-yet-unreleased;
- we bootified
valipby creating the correspondingbuild.bootbuild file and theboot.propertiesfile. - we tested the
valiplibrary in the context of themodern-cljsproject by installing it into the local maven repository.
In a real world scenario, the next step would be to submit a pull
request to the upstream valip repository, and wait until the owner
of the repo would eventually accept and merge our pull request.
But what if the owner is lazy or for any reason she/he does not agree to merge our pull requests?
Currently, the modified valip library lives on your computer only,
and it can't be directly shared with other developers or even with
other computers.
You have a couple of options:
- you can publish the updated library to clojars public repository. This way the library will be available to everybody;
- you can publish a lib on a private repository. This way the
accessibility to the updated
valiplibrary is governed by the rules defined in the repository itself. Generally, this is the right choice when you want to make a library available to other devs without making it public.
In the next part of this tutorial we're going to inspect the first option only.
A Quick Survival Guide
The process of publishing a CLJ/CLJS lib to clojars is pretty simple. As already said, keep in mind that any release ending in "-SNAPSHOT" is not an official release and you should rely on them only when you really need (which it is not our fictional scenario). Also remember that by adding a SNAPSHOT dependency to your project, you will cause any build tool to slow down its dependencies search.
Clojars offers two repositories, Classic and Releases. The Classic repository, which is the one we're going to use, has no restrictions and anyone may publish a lib to it.
That said, if you want to push your own version of somebody else's
library, which is our case, do not use the original groupId: use
your personal groupId, e.g., org.clojars.<your_clojars_name>.
bootlaces task
To publish a library to clojars's Classic Repository, you first need
to register with it and you're almost
ready. Actually, there is another very handy boot task to be used:
bootlaces.
The bootlaces task is aimed at simplifying the typical workflow of
publishing a library to clojars.
Open the build.boot file to add the bootlaces task and to require
its main namespace as well:
(set-env!
:source-paths #{"src"}
:dependencies '[...
[adzerk/bootlaces "0.1.13"]])
(require '...
'[adzerk.bootlaces :refer [bootlaces! build-jar push-snapshot]])
Now edit the valip's build.boot file as suggested by bootlaces's
README.md file
(set-env! ...)
(require ...)
(def +version+ "0.4.0-SNAPSHOT")
(bootlaces! +version+)
and you're ready to go:
boot build-jar push-snapshot
Writing pom.xml and pom.properties...
Writing valip-0.4.0-SNAPSHOT.jar...
Installing valip-0.4.0-SNAPSHOT.jar...
CLOJARS_USER and CLOJARS_PASS were not set; please enter your Clojars credentials.
Username:
Password:
clojure.lang.ExceptionInfo: java.lang.AssertionError: Assert failed: current git branch is reader-conditionals but must be master
(or (not ensure-branch) (= b ensure-branch))
data: {:file
"/var/folders/8z/yj2xnrdj0hb5mswc1kfyjmw00000gn/T/boot.user8233124078905763746.clj",
:line 33}
java.util.concurrent.ExecutionException: java.lang.AssertionError: Assert failed: current git branch is reader-conditionals but must be master
(or (not ensure-branch) (= b ensure-branch))
java.lang.AssertionError: Assert failed: current git branch is reader-conditionals but must be master
(or (not ensure-branch) (= b ensure-branch))
boot.task.built-in/fn/fn/fn/fn built_in.clj: 782
adzerk.bootlaces/eval715/fn/fn/fn bootlaces.clj: 54
adzerk.bootlaces/eval754/fn/fn/fn bootlaces.clj: 62
boot.task.built-in/fn/fn/fn/fn built_in.clj: 716
boot.task.built-in/fn/fn/fn/fn built_in.clj: 618
boot.task.built-in/fn/fn/fn/fn built_in.clj: 342
boot.core/run-tasks core.clj: 794
boot.core/boot/fn core.clj: 804
clojure.core/binding-conveyor-fn/fn core.clj: 1916
...
Uhm, not such a nice shot. After having required your clojars
credentials, boot complained about the fact that your current branch
is not the master. As default, bootlaces assumes that you only
publish a snapshot release from a master branch, but you can overwrite
the default with task-options!. Indeed, the push-snapshot task
internally uses the built-in push task:
(deftask push-snapshot
"Deploy snapshot version to Clojars."
[f file PATH str "The jar file to deploy."]
(comp (collect-clojars-credentials)
(push :file file :ensure-snapshot true)))
Now take a look at the push docstring and pay
attention on the --ensure-* options:
boot push -h
Deploy jar file to a Maven repository.
...
Options:
...
-B, --ensure-branch BRANCH Set the required current git branch to BRANCH.
-C, --ensure-clean Ensure that the project git repo is clean.
-R, --ensure-release Ensure that the current version is not a snapshot.
-S, --ensure-snapshot Ensure that the current version is a snapshot.
-T, --ensure-tag TAG Set the SHA1 of the commit the pom's scm tag must contain to TAG.
-V, --ensure-version VER Set the version the jar's pom must contain to VER.
Interesting, we can easily change the push task's behavior by just
setting the :ensure-branch options to nil in the task-options!
section we already used previosly to configure the pom, test and
test-cljs tasks:
(task-options!
push {:ensure-branch nil}
pom {...}
test {...}
test-cljs {...})
Shoot again:
boot build-jar push-snapshot
Writing pom.xml and pom.properties...
Writing valip-0.4.0-SNAPSHOT.jar...
Installing valip-0.4.0-SNAPSHOT.jar...
CLOJARS_USER and CLOJARS_PASS were not set; please enter your Clojars credentials.
Username:
Password:
clojure.lang.ExceptionInfo: java.lang.AssertionError: Assert failed: project repo is not clean
(or (not ensure-clean) clean?)
...
This time, the push-snapshot task complains about the fact that the git
branch is not clean. This is something that we should like, because
generally speaking you're not publishing something that is still to be
committed, right? So, let's be nice to ourselves and commit the work
we have done so far
cd /path/to/valip
git commit -am "prepare for publish to clojar"
and shoot the snapshot again:
boot build-jar push-snapshot
Writing pom.xml and pom.properties...
Writing valip-0.4.0-SNAPSHOT.jar...
Installing valip-0.4.0-SNAPSHOT.jar...
CLOJARS_USER and CLOJARS_PASS were not set; please enter your Clojars credentials.
Username:
Password:
Deploying valip-0.4.0-SNAPSHOT.jar...
Real world check
Even if we already checked the Reader Conditionals compliant valip
library within the modern-cljs context, we want to be sure
modern-cljs is still working with the new snaphost release of
valip by downloading it from clojars, instead of using the one we
installed in the local maven repository of our machine. The
accomplishment of this assignment is very easy.
First, delete the installed valip from the local maven repository
rm -rf ~/.m2/repository/org/clojars/<your_github_name/valip
then re-run the tdd task from the modern-cljs project home
directory:
cd /path/to/modern-cljs
boot tdd
Retrieving valip-0.4.0-20160111.164819-6.jar from https://clojars.org/repo/
...
Elapsed time: 36.476 sec
Boom! Did you note the Retrieving valip-.... notification? Now,
interactively test the usual
Shopping Calculator to verify
that everything is still working. Then stop the boot process, but if
you think you're done, you're wrong again!
Dependency scope
The very last topic of this tutorial has to do with dependency
management. boot, being based on maven, uses the same semantics as
maven when dealing with dependency scope. Take into account that
the dependency scope controls the dependency transitivity as well. In
maven there are 6 scopes available:
- compile
- provided
- runtime
- test
- system
- import
I have to admit that I never saw more than a couple of them, namely
"test" and "provided", in the context of boot build files and even
less with leiningen, which offers
profiles for such a thing.
NOTE 6: when you don't specify a
:scope,mavenassumes"compile"
Test Scope
The test scope indicates that a dependency is only required for the compilation and the test phases of the library itself and it's not required to consume the library from another application.
Let's illustrate this concept within the valip library by
analyzing its dependencies starting from the ones characterized by a
very clear role. All the boot tasks play a role in the building,
testing and publishing of a library, but they are not consumed by an
application using the library itself. We can safely say that all of
them should have the :scope set to "test"
Provided Scope
But what about Clojure and ClojureScript compilers? Obviously, we need
them to compile valip. Any application consuming the valip source
code needs to compile it as well, but it will provide those compilers by
itself. We can safely say that both the CLJ and the CLJS compilers should have the :scope set to "provided".
Here is the entire build.boot build file complete with dependencies scoping.
(set-env!
:source-paths #{"src"}
:dependencies '[[org.clojure/clojure "1.8.0" :scope "provided"]
[adzerk/boot-test "1.2.0" :scope "test"]
[org.clojure/clojurescript "1.9.494" :scope "provided"]
[adzerk/boot-cljs "1.7.228-2" :scope "test"]
[crisptrutski/boot-cljs-test "0.3.0" :scope "test"]
[doo "0.1.7" :scope "test"]
[adzerk/bootlaces "0.1.13" :scope "test"]])
(require '[adzerk.boot-test :refer [test]]
'[adzerk.boot-cljs :refer [cljs]]
'[crisptrutski.boot-cljs-test :refer [test-cljs]]
'[adzerk.bootlaces :refer [bootlaces! build-jar push-snapshot]])
(def +version+ "0.4.0-SNAPSHOT")
(bootlaces! +version+)
(task-options!
push {:ensure-branch nil}
pom {:project 'org.clojars.magomimmo/valip
:version +version+
:description "Functional validation library for Clojure and ClojureScript.
Forked from https://github.com/cemerick/valip"
:url "http://github.com/magomimmo/valip"
:scm {:url "http://github.com/magomimmo/valip"}
:license {"Eclipse Public License" "http://www.eclipse.org/legal/epl-v10.html"}}
test {:namespaces #{'valip.test.core 'valip.test.predicates}}
test-cljs {:namespaces #{'valip.test.core 'valip.test.predicates}})
(deftask testing
[]
(merge-env! :source-paths #{"test"})
identity)
(deftask tdd
"Launch a CLJ TDD Environment"
[]
(comp
(testing)
(watch)
(test-cljs)
(test)))
(deftask install-jar
[]
(merge-env! :resource-paths #{"src"})
(comp
(pom)
(jar)
(install)))
You can now re-publish valip to clojars
boot build-jar push-snapshot
Writing pom.xml and pom.properties...
Writing valip-0.4.0-SNAPSHOT.jar...
Installing valip-0.4.0-SNAPSHOT.jar...
CLOJARS_USER and CLOJARS_PASS were not set; please enter your Clojars credentials.
Username:
Password:
Deploying valip-0.4.0-SNAPSHOT.jar...
That's all folks.
Next Step - Tutorial 21 A reasoned porting of the Official React Tutorial to Reagent - Part I
The next tutorial will begin guiding you through porting the Official React tutorial to Reagent step by step.
License
Copyright © Mimmo Cosenza, 2012-15. Released under the Eclipse Public License, the same as Clojure.