From 013ba7136e2cd39618ce592b85af4b0e0f9a5eba Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Thu, 2 Feb 2017 15:43:29 +1300 Subject: [PATCH 1/3] Add support for lein checkouts - Add checkouts source directories to the classpath - Add checkouts source directories to :source-paths Fixes #9 --- README.md | 10 ++ plugin/project.clj | 1 + plugin/src/leiningen/figwheel.clj | 71 ++++++++++--- .../test-project-no-checkouts/.gitignore | 1 + .../test-project-no-checkouts/project.clj | 11 ++ .../test-project-no-checkouts/src/core.cljs | 3 + .../test-project-with-checkouts/.gitignore | 1 + .../checkouts/utils-lib | 1 + .../test-project-with-checkouts/project.clj | 11 ++ .../test-project-with-checkouts/src/core.cljs | 3 + plugin/test-resources/utils-lib/.gitignore | 1 + plugin/test-resources/utils-lib/project.clj | 11 ++ .../test-resources/utils-lib/src/utils.cljs | 3 + plugin/test/leiningen/figwheel_test.clj | 100 ++++++++++++++++-- 14 files changed, 205 insertions(+), 23 deletions(-) create mode 100644 plugin/test-resources/test-project-no-checkouts/.gitignore create mode 100644 plugin/test-resources/test-project-no-checkouts/project.clj create mode 100644 plugin/test-resources/test-project-no-checkouts/src/core.cljs create mode 100644 plugin/test-resources/test-project-with-checkouts/.gitignore create mode 120000 plugin/test-resources/test-project-with-checkouts/checkouts/utils-lib create mode 100644 plugin/test-resources/test-project-with-checkouts/project.clj create mode 100644 plugin/test-resources/test-project-with-checkouts/src/core.cljs create mode 100644 plugin/test-resources/utils-lib/.gitignore create mode 100644 plugin/test-resources/utils-lib/project.clj create mode 100644 plugin/test-resources/utils-lib/src/utils.cljs diff --git a/README.md b/README.md index 3612817c..9aae5c53 100644 --- a/README.md +++ b/README.md @@ -922,6 +922,16 @@ a great deal of programming complexity stems from complex interactions (side effecting events) between things that have local state, it is my belief that reloadable code is often simply better code. +## Checkouts + +Figwheel supports Leiningen's [checkouts][checkouts] +mechanism. This is useful when you want to make changes to a library +while you are developing an application that is using that library. +Figwheel will automatically pick up the :source-paths of any projects +symlinked in the `checkouts` directory. For full details, see +Leiningen's documentation on [checkouts][checkouts]. + +[checkouts]: (https://github.com/technomancy/leiningen/blob/master/doc/TUTORIAL.md#checkout-dependencies) ## More React Advocacy diff --git a/plugin/project.clj b/plugin/project.clj index 637d51ac..b2c4aede 100644 --- a/plugin/project.clj +++ b/plugin/project.clj @@ -12,6 +12,7 @@ :profiles {:dev {:dependencies [[leiningen "2.7.1"] [org.clojure/test.check "0.9.0"]] + :resource-paths ["test-resources"] :source-paths ["dev" "src"] :aliases {"change-version" ["run" "-m" "figwheel-tasks.core" ":change-version"] "install-all" ["run" "-m" "figwheel-tasks.core" ":install-all"]}}} diff --git a/plugin/src/leiningen/figwheel.clj b/plugin/src/leiningen/figwheel.clj index b9d39b89..5014d1cd 100644 --- a/plugin/src/leiningen/figwheel.clj +++ b/plugin/src/leiningen/figwheel.clj @@ -3,12 +3,15 @@ (:require #_[clojure.pprint :as pp] [leiningen.core.eval :as leval] + [leiningen.core.project :as lproject] [leiningen.clean :as clean] [leiningen.core.main :as main] [clojure.java.io :as io] [clojure.set :refer [intersection]] [leiningen.figwheel.fuzzy :as fuz] - [simple-lein-profile-merge.core :as lm])) + [simple-lein-profile-merge.core :as lm]) + (:import (java.io File) + (java.nio.file Path))) (def _figwheel-version_ "0.5.15-SNAPSHOT") @@ -284,6 +287,44 @@ (when (every? not-empty args) (not-empty (apply intersection (map set args))))) +(defn checkout-source-paths + "Get source paths for all of the lein projects in the checkouts directory." + [project] + (let [checkout-project-maps (lproject/read-checkouts project) + checkout-sources (for [co-project checkout-project-maps + source-path (:source-paths co-project)] + ;; Make the checkout source path pretty, e.g. checkouts/utils-lib/src + (str (.relativize (.toPath (io/file (:root project))) ;; Note, root of the parent project, not the checkout project + (.toPath (io/file source-path)))))] + (distinct checkout-sources))) + +(defn map-vals + "Returns a hashmap consisting of the result of applying f to + the value of each set in hashmap. + Function f should accept one single argument." + [f m] + (persistent! + (reduce-kv (fn [m k v] (assoc! m k (f v))) + (transient (empty m)) m))) + +(defn update-builds + "Map a function across each cljsbuild build config. + + The :cljsbuild :builds path can either be a vector of builds, + or a map of build-ids to builds. This function handles both variants." + [project f] + (if-let [builds (get-in project [:cljsbuild :builds])] + (assoc-in project + [:cljsbuild :builds] + (if (map? builds) + (map-vals f builds) + (map f builds))) + project)) + +(defn add-source-paths [project source-paths] + (update-builds project + (fn [build] (update build :source-paths (fn [existing-paths] (reduce conj existing-paths source-paths)))))) + (defn source-paths-for-classpath [{:keys [figwheel-options all-builds build-ids] :as data}] (if-not (and all-builds (not-empty all-builds)) [] @@ -398,21 +439,25 @@ (defn build-once [project build-ids] (when-not (report-if-bad-build-ids project build-ids) - (run-build-once - project - (fuzzy-config-from-project project) - (source-paths-for-classpath - (normalize-data project build-ids)) - (vec build-ids)))) + (let [checkout-sources (checkout-source-paths project) + project (add-source-paths project checkout-sources)] + (run-build-once + project + (fuzzy-config-from-project project) + (source-paths-for-classpath + (normalize-data project build-ids)) + (vec build-ids))))) (defn figwheel-main [project build-ids] (when-not (report-if-bad-build-ids project build-ids) - (run-figwheel - project - (fuzzy-config-from-project project) - (source-paths-for-classpath - (normalize-data project build-ids)) - (vec build-ids)))) + (let [checkout-sources (checkout-source-paths project) + project (add-source-paths project checkout-sources)] + (run-figwheel + project + (-> project fuzzy-config-from-project) + (source-paths-for-classpath + (normalize-data project build-ids)) + (vec build-ids))))) (defmulti fig-dispatch (fn [command _ _] command)) diff --git a/plugin/test-resources/test-project-no-checkouts/.gitignore b/plugin/test-resources/test-project-no-checkouts/.gitignore new file mode 100644 index 00000000..2f7896d1 --- /dev/null +++ b/plugin/test-resources/test-project-no-checkouts/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/plugin/test-resources/test-project-no-checkouts/project.clj b/plugin/test-resources/test-project-no-checkouts/project.clj new file mode 100644 index 00000000..9d93bef8 --- /dev/null +++ b/plugin/test-resources/test-project-no-checkouts/project.clj @@ -0,0 +1,11 @@ +(defproject test-project-no-checkouts "1.0.0" + :description "A test project for testing Figwheel when there are no checkouts." + :dependencies [[org.clojure/clojure "1.9.0-alpha15"] + [org.clojure/clojurescript "1.9.908"]] + :source-paths ["src"] + :cljsbuild {:builds {:dev {:source-paths ["src"] + :compiler {:main core + :asset-path "js/out" + :output-to "resources/public/js/example.js" + :output-dir "resources/public/js/out" + :optimizations :none}}}}) diff --git a/plugin/test-resources/test-project-no-checkouts/src/core.cljs b/plugin/test-resources/test-project-no-checkouts/src/core.cljs new file mode 100644 index 00000000..675d42f6 --- /dev/null +++ b/plugin/test-resources/test-project-no-checkouts/src/core.cljs @@ -0,0 +1,3 @@ +(ns core) + +;; Intentionally left blank diff --git a/plugin/test-resources/test-project-with-checkouts/.gitignore b/plugin/test-resources/test-project-with-checkouts/.gitignore new file mode 100644 index 00000000..2f7896d1 --- /dev/null +++ b/plugin/test-resources/test-project-with-checkouts/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/plugin/test-resources/test-project-with-checkouts/checkouts/utils-lib b/plugin/test-resources/test-project-with-checkouts/checkouts/utils-lib new file mode 120000 index 00000000..41c9456f --- /dev/null +++ b/plugin/test-resources/test-project-with-checkouts/checkouts/utils-lib @@ -0,0 +1 @@ +../../utils-lib/ \ No newline at end of file diff --git a/plugin/test-resources/test-project-with-checkouts/project.clj b/plugin/test-resources/test-project-with-checkouts/project.clj new file mode 100644 index 00000000..1fa462da --- /dev/null +++ b/plugin/test-resources/test-project-with-checkouts/project.clj @@ -0,0 +1,11 @@ +(defproject test-project-with-checkouts "1.0.0" + :description "A test project for testing Figwheel with checkouts." + :dependencies [[org.clojure/clojure "1.9.0-alpha15"] + [org.clojure/clojurescript "1.9.908"]] + :source-paths ["src"] + :cljsbuild {:builds {:dev {:source-paths ["src"] + :compiler {:main core + :asset-path "js/out" + :output-to "resources/public/js/example.js" + :output-dir "resources/public/js/out" + :optimizations :none}}}}) diff --git a/plugin/test-resources/test-project-with-checkouts/src/core.cljs b/plugin/test-resources/test-project-with-checkouts/src/core.cljs new file mode 100644 index 00000000..675d42f6 --- /dev/null +++ b/plugin/test-resources/test-project-with-checkouts/src/core.cljs @@ -0,0 +1,3 @@ +(ns core) + +;; Intentionally left blank diff --git a/plugin/test-resources/utils-lib/.gitignore b/plugin/test-resources/utils-lib/.gitignore new file mode 100644 index 00000000..2f7896d1 --- /dev/null +++ b/plugin/test-resources/utils-lib/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/plugin/test-resources/utils-lib/project.clj b/plugin/test-resources/utils-lib/project.clj new file mode 100644 index 00000000..9ae9279d --- /dev/null +++ b/plugin/test-resources/utils-lib/project.clj @@ -0,0 +1,11 @@ +(defproject utils-lib "1.0.0" + :description "A test project to be consumed as a checkouts project." + :dependencies [[org.clojure/clojure "1.9.0-alpha15"] + [org.clojure/clojurescript "1.9.908"]] + :source-paths ["src"] + :cljsbuild {:builds {:dev {:source-paths ["src"] + :compiler {:main utils + :asset-path "js/out" + :output-to "resources/public/js/example.js" + :output-dir "resources/public/js/out" + :optimizations :none}}}}) diff --git a/plugin/test-resources/utils-lib/src/utils.cljs b/plugin/test-resources/utils-lib/src/utils.cljs new file mode 100644 index 00000000..3e6bf721 --- /dev/null +++ b/plugin/test-resources/utils-lib/src/utils.cljs @@ -0,0 +1,3 @@ +(ns utils) + +;; Intentionally left blank diff --git a/plugin/test/leiningen/figwheel_test.clj b/plugin/test/leiningen/figwheel_test.clj index b8a6f261..7e7bb439 100644 --- a/plugin/test/leiningen/figwheel_test.clj +++ b/plugin/test/leiningen/figwheel_test.clj @@ -1,11 +1,12 @@ (ns leiningen.figwheel-test (:require - [leiningen.figwheel :as f] - [clojure.test :as t :refer [deftest is testing run-tests]] - [clojure.test.check :as tc] - [clojure.test.check.generators :as gen] - [clojure.test.check.properties :as prop] - [clojure.test.check.clojure-test :refer [defspec]])) + [leiningen.figwheel :as f] + [clojure.test :as t :refer [deftest is testing run-tests]] + [clojure.test.check :as tc] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as prop] + [clojure.test.check.clojure-test :refer [defspec]] + [clojure.java.io :as io])) (def iterations 10) @@ -132,7 +133,86 @@ (gen/vector (gen/map (gen/return :source-paths) (gen/vector gen/string))))) - - - - +(deftest update-builds-test + (testing "vector of builds" + (is (= (f/update-builds {:cljsbuild {:builds [{:id "dev"} + {:id "prod"} + {:id "test"}]}} + (fn [build] + (update build :source-paths conj "src"))) + {:cljsbuild {:builds [{:id "dev" + :source-paths ["src"]} + {:id "prod" + :source-paths ["src"]} + {:id "test" + :source-paths ["src"]}]}}))) + (testing "map of builds" + (is (= (f/update-builds {:cljsbuild {:builds {"dev" {} + "prod" {} + "test" {}}}} + (fn [build] + (update build :source-paths conj "src"))) + {:cljsbuild {:builds {"dev" {:source-paths ["src"]} + "prod" {:source-paths ["src"]} + "test" {:source-paths ["src"]}}}}))) + (testing "edge cases around no builds" + (testing "vector of builds" + (is (= (f/update-builds {:cljsbuild {:builds []}} + (fn [x] + 1)) + {:cljsbuild {:builds []}})) + (is (= (f/update-builds {:cljsbuild {}} + (fn [x] + 1)) + {:cljsbuild {}}))) + (testing "map of builds" + (is (= (f/update-builds {:cljsbuild {:builds {}}} + (fn [x] + 1)) + {:cljsbuild {:builds {}}})) + (is (= (f/update-builds {:cljsbuild {}} + (fn [x] + 1)) + {:cljsbuild {}}))))) + +(deftest add-source-paths-test + (testing "vector of builds" + (is (= (f/add-source-paths + {:cljsbuild {:builds [{:id "dev" + :source-paths ["src/cljs" "src/cljc" "dev"] + :resource-paths ["resources"]} + {:id "prod" + :source-paths ["src/cljs" "src/cljc" "prod"] + :resource-paths ["resources"]}]}} + ["checkouts/util-lib/src"]) + {:cljsbuild {:builds [{:id "dev" + :source-paths ["src/cljs" "src/cljc" "dev" "checkouts/util-lib/src"] + :resource-paths ["resources"]} + {:id "prod" + :source-paths ["src/cljs" "src/cljc" "prod" "checkouts/util-lib/src"] + :resource-paths ["resources"]}]}})))) + +(deftest checkout-source-paths-test + (let [cwd (.getCanonicalFile (io/file "."))] + (testing "test project with checkouts" + (is (= (f/checkout-source-paths + {:root (str cwd "/test-resources/test-project-with-checkouts") + :source-paths ["src"] + :cljsbuild {:builds {:dev {:source-paths ["src"] + :compiler {:main 'core + :asset-path "js/out" + :output-to "resources/public/js/example.js" + :output-dir "resources/public/js/out" + :optimizations :none}}}}}) + ["checkouts/utils-lib/src"]))) + (testing "test project with no checkouts" + (is (= (f/checkout-source-paths + {:root (str cwd "/test-resources/test-project-with-no-checkouts") + :source-paths ["src"] + :cljsbuild {:builds {:dev {:source-paths ["src"] + :compiler {:main 'core + :asset-path "js/out" + :output-to "resources/public/js/example.js" + :output-dir "resources/public/js/out" + :optimizations :none}}}}}) + []))))) From c904faa7167d50deac0042a02585cde4263a0e95 Mon Sep 17 00:00:00 2001 From: Alistair Roche Date: Sat, 28 Apr 2018 17:12:59 +0800 Subject: [PATCH 2/3] Fix test that failed due to .toPath following checkout symlink --- plugin/src/leiningen/figwheel.clj | 12 +++++++----- plugin/test/leiningen/figwheel_test.clj | 8 ++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/plugin/src/leiningen/figwheel.clj b/plugin/src/leiningen/figwheel.clj index 5014d1cd..6bf1f826 100644 --- a/plugin/src/leiningen/figwheel.clj +++ b/plugin/src/leiningen/figwheel.clj @@ -291,11 +291,13 @@ "Get source paths for all of the lein projects in the checkouts directory." [project] (let [checkout-project-maps (lproject/read-checkouts project) - checkout-sources (for [co-project checkout-project-maps - source-path (:source-paths co-project)] - ;; Make the checkout source path pretty, e.g. checkouts/utils-lib/src - (str (.relativize (.toPath (io/file (:root project))) ;; Note, root of the parent project, not the checkout project - (.toPath (io/file source-path)))))] + parent-project-root-path (.toPath (io/file (:root project))) + checkout-sources (for [co-project checkout-project-maps + source-path-str (:source-paths co-project) + :let [source-path (.toPath (io/file source-path-str))]] + ;; Make the checkout source path pretty, e.g. ../utils-lib/src + ;; NOTE: this follows the symlink at checkouts/utils-lib + (str (.relativize parent-project-root-path source-path)))] (distinct checkout-sources))) (defn map-vals diff --git a/plugin/test/leiningen/figwheel_test.clj b/plugin/test/leiningen/figwheel_test.clj index 7e7bb439..6d30f1b7 100644 --- a/plugin/test/leiningen/figwheel_test.clj +++ b/plugin/test/leiningen/figwheel_test.clj @@ -184,12 +184,12 @@ {:id "prod" :source-paths ["src/cljs" "src/cljc" "prod"] :resource-paths ["resources"]}]}} - ["checkouts/util-lib/src"]) + ["../util-lib/src"]) {:cljsbuild {:builds [{:id "dev" - :source-paths ["src/cljs" "src/cljc" "dev" "checkouts/util-lib/src"] + :source-paths ["src/cljs" "src/cljc" "dev" "../util-lib/src"] :resource-paths ["resources"]} {:id "prod" - :source-paths ["src/cljs" "src/cljc" "prod" "checkouts/util-lib/src"] + :source-paths ["src/cljs" "src/cljc" "prod" "../util-lib/src"] :resource-paths ["resources"]}]}})))) (deftest checkout-source-paths-test @@ -204,7 +204,7 @@ :output-to "resources/public/js/example.js" :output-dir "resources/public/js/out" :optimizations :none}}}}}) - ["checkouts/utils-lib/src"]))) + ["../utils-lib/src"]))) (testing "test project with no checkouts" (is (= (f/checkout-source-paths {:root (str cwd "/test-resources/test-project-with-no-checkouts") From 23bed0961dd2a90d99e4ca118c7b9be537840596 Mon Sep 17 00:00:00 2001 From: Alistair Roche Date: Sat, 28 Apr 2018 17:35:11 +0800 Subject: [PATCH 3/3] Only add checkout source paths if explicit figwheel option set --- plugin/src/leiningen/figwheel.clj | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugin/src/leiningen/figwheel.clj b/plugin/src/leiningen/figwheel.clj index 6bf1f826..ff9114a6 100644 --- a/plugin/src/leiningen/figwheel.clj +++ b/plugin/src/leiningen/figwheel.clj @@ -441,8 +441,9 @@ (defn build-once [project build-ids] (when-not (report-if-bad-build-ids project build-ids) - (let [checkout-sources (checkout-source-paths project) - project (add-source-paths project checkout-sources)] + (let [project (if (:add-checkout-source-paths (figwheel-options project)) + (add-source-paths project (checkout-source-paths project)) + project)] (run-build-once project (fuzzy-config-from-project project) @@ -452,8 +453,9 @@ (defn figwheel-main [project build-ids] (when-not (report-if-bad-build-ids project build-ids) - (let [checkout-sources (checkout-source-paths project) - project (add-source-paths project checkout-sources)] + (let [project (if (:add-checkout-source-paths (figwheel-options project)) + (add-source-paths project (checkout-source-paths project)) + project)] (run-figwheel project (-> project fuzzy-config-from-project)