├── .buckconfig ├── .gitignore ├── .travis.yml ├── BUCK ├── LICENSE ├── README.md ├── build.cljs ├── clj-cljs-config ├── BUCK.example ├── config.py ├── figwheel-index.html ├── project-clj.clj ├── project-cljs.clj └── project-repl.clj ├── lib.py ├── tester-lein-clj.sh ├── tester-lein-cljc-doo.sh ├── tester-lein-cljc-planck.sh ├── tester-lein-cljs-doo.sh ├── tester-lein-cljs-planck.sh └── tests ├── BUCK.tests ├── a_clj.clj ├── a_cljc.cljc ├── a_cljs.cljs ├── b.cljs ├── b_clj.clj ├── b_cljc.cljc ├── b_cljs.cljs ├── c.cljc ├── c_clj.clj ├── c_cljc.cljc ├── c_cljs.cljs ├── d1.clj ├── d1.cljc ├── d1.cljs ├── d2.clj ├── d2.cljc ├── d2.cljs ├── e_clj.clj ├── e_cljc.cljc ├── e_cljs.cljs ├── e_test.clj ├── e_test.cljc ├── e_test.cljs ├── f_clj.clj ├── f_cljc.cljc ├── f_cljs.cljs ├── g1_test.clj ├── g1_test.cljc ├── g1_test.cljs ├── g2_test.clj ├── g2_test.cljc ├── g2_test.cljs ├── g_clj.clj ├── g_cljc.cljc ├── g_cljs.cljs └── gen-out.sh /.buckconfig: -------------------------------------------------------------------------------- 1 | [buildfile] 2 | includes = //clj-cljs-config/config.py -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | pom.xml.asc 3 | *jar 4 | /lib/ 5 | /classes/ 6 | /target/ 7 | /checkouts/ 8 | .lein-deps-sum 9 | .lein-repl-history 10 | .lein-plugins/ 11 | .lein-failures 12 | .nrepl-port 13 | buck-out 14 | .buckd 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | cache: 3 | directories: 4 | - /Library/Caches/Homebrew 5 | - .m2 6 | install: 7 | - brew update && brew tap facebook/fb 8 | - brew install buck leiningen phantomjs planck 9 | script: 10 | - mv tests/BUCK.tests tests/BUCK 11 | - mv clj-cljs-config/BUCK.example clj-cljs-config/BUCK 12 | - TERM=dumb buck build "//..." && TERM=dumb buck test "//..." 13 | -------------------------------------------------------------------------------- /BUCK: -------------------------------------------------------------------------------- 1 | export_file(name = 'build.cljs', 2 | visibility = ['PUBLIC']) 3 | 4 | genrule(name = 'builder-planck', 5 | srcs = [], 6 | # HACK: If we change `build.cljs` file then `build` target would be updated, but because 7 | # it will generate exactly the same output (planck script invocation is the same) then 8 | # no rebuild will occur for dependent targets. Here we force it by including md5 checksum 9 | # into the script file as a comment 10 | # Note: --auto-cache --cache=$SRCDIR --static-fns optimisations doesn't bring anything 11 | bash = 'echo "planck $(location :build.cljs) \$@" > $OUT && ' + 12 | 'echo "#`cat $(location :build.cljs) | md5`" >> $OUT && ' + 13 | 'chmod +x $OUT', 14 | out = 'build.sh', 15 | executable = True, 16 | visibility = ['PUBLIC']) 17 | 18 | for name in ['tester-lein-clj.sh', 19 | 'tester-lein-cljs-doo.sh', 20 | 'tester-lein-cljs-planck.sh', 21 | 'tester-lein-cljc-doo.sh', 22 | 'tester-lein-cljc-planck.sh']: 23 | export_file(name = name.split('.')[0], 24 | src = name, 25 | out = name, 26 | visibility = ['PUBLIC']) 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Artem Yarulin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clojure-ClojureScript-Buck 2 | 3 | [![Build Status](https://travis-ci.org/artemyarulin/clojure-clojurescript-buck.svg?branch=master)](https://travis-ci.org/artemyarulin/clojure-clojurescript-buck) 4 | 5 | Clojure and ClojureScript support for [Buck build system](https://buckbuild.com). If you have Clojure/ClojureScript and monorepo then it's a thing to check 6 | 7 | ## Features 8 | 9 | - Build Clojure/ClojureScript 10 | - Run tests for Clojure and choose what test runner to use with ClojureScript: [doo](https://github.com/bensu/doo) or [Planck](http://planck-repl.org/testing.html)! 11 | - Build and test any Clojure/ClojureScript with always the same command: `buck build [module-name] && buck test [module-name]-test` 12 | - Run REPL into the module without too much thinking about details: `buck run [module-name]-repl` 13 | - All the features from [Buck build system](https://buckbuild.com) - it's a peace of cake! 14 | 15 | ## What problems does it solve 16 | 17 | - It allows you to use Clojure/ClojureScript inside a monorepo where different projects may depends on each other 18 | - With project based approach where you have one Leningen/Boot project per application/library it's sometimes difficult to reuse some part of the code between projects. Buck instead encourages you to create many small independent modules with their own dependencies/source/tests which will improve your code reuse 19 | - Buck allows to abstract build/test steps into functions that can be used later on across your repo 20 | 21 | ## Installation and requirements 22 | 23 | Currently only MacOS is supported. Linux/Windows support is covered in this [issue 18](https://github.com/artemyarulin/clojure-clojurescript-buck/issues/18) 24 | 25 | - Put content of this repo to the right place in your monorepo 26 | - Rename `clj-cljs-config/BUCK.example` to `clj-cljs-config/BUCK` to make it processable by Buck 27 | - Change `.buckconfig` so that it will include your config file by default: 28 | 29 | ``` 30 | [buildfile] 31 | includes = //path/to-config/clj-cljs-config/config.py 32 | ``` 33 | 34 | Once it's done check `clj-cljs-config/config.py` - most likely you gonna need to change paths. Feel free to add any additional build logic there 35 | 36 | ## Configuration 37 | 38 | Idea it that `clj_cljs_module` is low level function and it's used always via some wrapper functions like in [RULES/clj-cljs-config/config.py](RULES/clj-cljs-config/config.py) where you can specify custom project files, default dependencies (Clojure/ClojureScript versions), different builders or testers 39 | 40 | ## How does it work 41 | 42 | Under the hood we simply create Leiningen project and put files and parameters in the right place. 43 | 44 | Entry point would be your [custom wrapper](RULES/clj-cljs-config/config.py) with supplied project file on top of `clj_cljs_module` function in [lib.py](RULES/clj-cljs/lib.py), which in turn will save all supplied parameters to `info` file which then would be executed by builder. For now only Planck based [builder.cljs](RULES/clj-cljs/build.cljs) is available. 45 | 46 | `builder` then: 47 | - Create a normal folder structure (with source files placed in a right sub-folder, etc.) 48 | - Collect all sub-dependencies 49 | - Create entry point file which requires all the existing module namespaces (including tests) which simplifies REPL and testing 50 | - Update project file with actual data 51 | 52 | ## Status 53 | 54 | Foundation is solid and unlikely that API gonna change in near feature. It's a second big rewrite already so most of the edge cases should be covered. Although it still missing some important things like [Figwheel support](https://github.com/artemyarulin/clojure-clojurescript-buck/issues/19) 55 | 56 | ## Alternatives 57 | 58 | - [lein-monolith](https://github.com/amperity/lein-monolith) from Amperity - is a Leiningen plugin to work with multiple projects inside a monorepo. Doesn't require any additional tools but Leiningen, much easier to start with, although it still uses project approach. 59 | 60 | - [Ladder developer mentioned on HN](https://news.ycombinator.com/item?id=11507975) that they have their own solution for CLJ/CLJS + Buck which looks awesome but not yet open sourced and includes some hacks in CLJS compiler. 61 | 62 | - [make](https://www.gnu.org/software/make/) - there are no tasks that you cannot do with make. If you like bare metal - then check [version 1.0.0](https://github.com/artemyarulin/clojure-clojurescript-buck/tree/1.0.0), it was implemented with power of shell,sed,grep and regexps. 63 | -------------------------------------------------------------------------------- /build.cljs: -------------------------------------------------------------------------------- 1 | (ns build.core 2 | (:require [planck.core :as core] 3 | [planck.io :as io] 4 | [planck.shell :as shell] 5 | [cljs.tools.reader :as reader] 6 | [clojure.string :as string])) 7 | 8 | ;; Helpers 9 | (def make-dirs (partial shell/sh "mkdir" "-p")) 10 | (def pwd (-> "pwd" shell/sh :out string/trim)) 11 | (defn copy [from to] (shell/sh "cp" "-r" from to)) 12 | (defn symlink [from to] (shell/sh "ln" from to)) 13 | (defn path-join [& args] (string/join "/" args)) 14 | (defn delete-last-path-component [p] (-> p (string/split "/") butlast (#(string/join "/" %)))) 15 | (defn delete-path [path] (shell/sh "rm" "-rf" path)) 16 | (defn pad-left [s len char] (if (< (count s) len) (recur (str char s) len char) s)) 17 | 18 | (defn path-from-content-namespace 19 | "Given basepath and file content will parse content and return new 20 | path based on file namespace" 21 | [base-path file-content] 22 | (letfn [(is-ns? [form] (and (list? form) (= (first form) 'ns) (< 1 (count form)))) 23 | (parse-ns [form] (cond (is-ns? form) (-> form second str) 24 | (list? form) (some parse-ns form)))] 25 | (->> file-content 26 | (#(str "(\n" % "\n)")) ;; Workaround if content has more than one top level s-exp, otherwise read-string will return only first one 27 | (reader/read-string {:read-cond :allow :features #{:clj :cljs}}) 28 | parse-ns 29 | (#(string/split % ".")) 30 | butlast 31 | (#(apply path-join base-path %))))) 32 | 33 | (defn organize-sources 34 | "Given source files and destination will go though all source files 35 | and exec copy-cmd for each file with original and new path which 36 | will be created based on a file namespace" 37 | [base-path files to copy-cmd] 38 | (letfn [(copy-source [source-file path-to] 39 | (let [source-path (.-path source-file) 40 | source-name (-> source-path (string/split "/") last)] 41 | (make-dirs path-to) 42 | (copy-cmd source-path (path-join path-to source-name)))) 43 | (find-out-path [file] 44 | (if (->> file :path (re-find #"clj$|cljs$|cljc$")) 45 | (->> file core/slurp (path-from-content-namespace to)) 46 | (-> file :path (string/replace base-path "") (string/split "/") rest butlast 47 | ((fn[path-parts] 48 | ;; HACK: If we would use clj_module(src=glob(['src/**/*'])) then Buck would copy 49 | ;; everything under src folder, but root folder would be still src, same for tests. 50 | ;; So here we just flatten folders together in order to avoid paths like module/src/src/file 51 | (if (= (first path-parts) (last (string/split to "/"))) 52 | (rest path-parts) 53 | path-parts))) 54 | (#(apply path-join to %)))))] 55 | (->> files 56 | (filter #(not (io/directory? %))) 57 | (mapv #(->> % find-out-path (copy-source %)))))) 58 | 59 | (defn organize-deps 60 | "Read deps file looking for sub-dependencies and merge all of them 61 | back into deps file" 62 | [deps-file] 63 | (letfn [(read-subdeps [path] 64 | (let [subdep-file (path-join path "deps")] 65 | (if (io/file-attributes subdep-file) 66 | (-> subdep-file core/slurp string/split-lines) 67 | [])))] 68 | (->> (core/slurp deps-file) 69 | string/split-lines 70 | (map read-subdeps) 71 | (apply concat) 72 | distinct 73 | (string/join "\n") 74 | (core/spit deps-file)))) 75 | 76 | (defn merge-deps-src 77 | "Merge deps source into current module src folder" 78 | [deps-file to] 79 | (->> (core/slurp deps-file) 80 | string/split-lines 81 | (mapv #(copy (path-join % "src") to)))) 82 | 83 | (defn update-project-file 84 | "Updates project file and replace tokens there with supplied data" 85 | [name main path] 86 | (let [project-file (path-join path "project.clj")] 87 | (-> (core/slurp project-file) 88 | (string/replace "{{name}}" name) 89 | (string/replace "{{main}}" main) 90 | (string/replace "{{deps}}" (core/slurp (path-join path "deps"))) 91 | (#(core/spit project-file %))))) 92 | 93 | (defn ensure-main-exists 94 | "Creates entry point file which requires all the existing module 95 | namespaces (including tests) which simplifies REPL and testing. Used 96 | as main if no main was supplied" 97 | [main path type] 98 | (let [def-main "module.core" 99 | find-all-namespaces (fn[path] 100 | (->> (shell/sh "find" path "-type" "f" "-name" "*.cljc" "-o" "-name" (str "*." type)) 101 | :out 102 | string/split-lines 103 | (map #(-> % 104 | (string/replace path "") 105 | (string/replace "/" ".") 106 | (string/replace "_" "-") 107 | (string/split ".") 108 | butlast 109 | rest)) 110 | (map #(string/join "." %)))) 111 | main-file (fn[namespaces] 112 | (str "(ns " def-main " (:require " 113 | (string/join "\n" (map #(str "[" % "]") namespaces)) 114 | "))")) 115 | main-path (path-join path "src" "module")] 116 | (make-dirs main-path) 117 | (->> (concat (find-all-namespaces (path-join path "src")) 118 | (find-all-namespaces (path-join path "test"))) 119 | (filter (complement string/blank?)) 120 | (filter (partial not= "deps")) 121 | main-file 122 | (core/spit (path-join main-path (str "core." type)))) 123 | (if (string/blank? main) 124 | def-main 125 | main))) 126 | 127 | (defn get-all-project-deps [] 128 | (shell/sh "buck" "build" "//ext:") ;; Ensure that all exts got built first 129 | (->> (shell/sh "buck" "targets" "//ext:" "--show-output" "--verbose" "0") 130 | :out 131 | string/split-lines 132 | (map #(string/split % " ")) 133 | (map second) 134 | (map #(path-join pwd % "deps")) 135 | (map core/slurp) 136 | string/join)) 137 | 138 | (defn run-repl [args] 139 | (let [[_ project-file resource output-folder query] args 140 | targets (->> query (shell/sh "buck" "query") :out string/split-lines) 141 | source-dest-path (path-join pwd output-folder "src") 142 | resource-dest-path (path-join pwd output-folder "resources")] 143 | (delete-path source-dest-path) 144 | (delete-path resource-dest-path) 145 | (loop [targets' targets counter 1] 146 | (when-let [target (first targets')] 147 | (println (str "[" (-> counter str (pad-left (-> targets count str count) " ")) "/" (count targets) "] Linking " target)) 148 | (let [buildfile-path (->> (shell/sh "buck" "query" (str "buildfile('" target "')")) :out (path-join pwd) delete-last-path-component) 149 | target-files (->> (shell/sh "buck" "query" (str "labels(srcs,deps('" target "'))")) :out string/split-lines (map #(path-join pwd %)))] 150 | (organize-sources buildfile-path 151 | (map io/file target-files) 152 | source-dest-path 153 | symlink) 154 | (organize-sources buildfile-path 155 | (->> target-files 156 | (filter #(not (re-find #"clj$|cljs$|cljc$" %))) 157 | (map io/file)) 158 | resource-dest-path 159 | symlink)) 160 | (recur (rest targets') (inc counter)))) 161 | (organize-sources (delete-last-path-component resource) [(io/file resource)] (path-join resource-dest-path "public") symlink) 162 | (ensure-main-exists nil output-folder "cljs") 163 | (-> (core/slurp project-file) 164 | (string/replace "{{deps}}" (get-all-project-deps)) 165 | (#(core/spit (path-join output-folder "project.clj") %))))) 166 | 167 | (let [args core/*command-line-args*] 168 | (case (first args) 169 | "repl" (run-repl args) 170 | ;; We cannot run Buck commands while we are inside a command which is running by Buck again 171 | ;; As a workaround we print the actual command to execute, so we can still use it like 172 | ;; $(buck run repl -- "//...") 173 | "repl-init" (print (string/join " " (apply vector (nth args 1) "repl" (subvec (vec args) 2)))) 174 | (let [parse-args #(let [info-file (-> % first core/slurp string/trim)] 175 | (zipmap [:name :type :main :src :out :task] 176 | (conj (string/split info-file ";") (second %)))) 177 | {:keys [src out type task name main]} (parse-args args) 178 | build? (= task "build")] 179 | (organize-sources src (core/file-seq src) (path-join out (if build? "src" "test")) symlink) 180 | (merge-deps-src (path-join out "deps") out) 181 | (organize-deps (path-join out "deps")) 182 | (update-project-file name (if build? name (ensure-main-exists main out type)) out)))) 183 | -------------------------------------------------------------------------------- /clj-cljs-config/BUCK.example: -------------------------------------------------------------------------------- 1 | # Project files 2 | for name in ['project-clj.clj','project-cljs.clj','project-repl.clj']: 3 | export_file(name = name.split('.')[0], 4 | src = name, 5 | out = name, 6 | visibility = ['PUBLIC']) 7 | 8 | # Figwheel starting point 9 | export_file(name = 'figwheel-index', 10 | src = 'figwheel-index.html', 11 | out = 'index.html', 12 | visibility = ['PUBLIC']) 13 | 14 | clj_cljs_repl(name = 'repl', 15 | builder = '//:builder-planck', 16 | project_file = ':project-repl', 17 | resource = ':figwheel-index', 18 | output_folder = '.repl') 19 | -------------------------------------------------------------------------------- /clj-cljs-config/config.py: -------------------------------------------------------------------------------- 1 | # Here goes example wrappers that you are free to modify depending on 2 | # your needs. It's not meant to be updated with new releases of 3 | # clj-cljs-buck: Use it as an example, change it and store in your 4 | # repo 5 | 6 | include_defs('//lib.py') 7 | 8 | # Set of helpers 9 | def ext(name): 10 | return '//tests:' + name 11 | def executer(name): 12 | return '//:' + name 13 | def resource(name): 14 | return '//clj-cljs-config:' + name 15 | def ensure_list(i): 16 | return i if isinstance(i,list) else [i] 17 | cljs_deps = [ext('org.clojure/clojure'), 18 | ext('org.clojure/clojurescript'), 19 | ext('figwheel-sidecar'), 20 | ext('com.cemerick/piggieback')] 21 | builder = executer('builder-planck') 22 | 23 | def module(ext,project_file,name,src,main,resources,modules,tests,test_resources,test_modules,tester,tester_args=[]): 24 | """Module creator helper. ensure_list that ensures that item is a list 25 | or wraps item with it. Most often you want to use one source file, 26 | one test, one module dependency so you can use 27 | clj_module(src='a.clj',tests='test.clj',modules=':b') without 28 | wrapping each with []. Also allows specifiying module without 29 | source files. name + ext will be used instead 30 | """ 31 | src = [name.replace('-','_') + '.' + ext] if src == None else ensure_list(src) 32 | 33 | clj_cljs_module(ext = ext, 34 | project_file = resource(project_file), 35 | builder = builder, 36 | name = name, 37 | resources = ensure_list(resources), 38 | src = src, 39 | modules = ensure_list(modules), 40 | main = main) 41 | if tests: 42 | clj_cljs_module(ext = ext, 43 | project_file = resource(project_file), 44 | builder = builder, 45 | name = name, 46 | resources = ensure_list(test_resources), 47 | src = ensure_list(tests), 48 | modules = ensure_list(test_modules) + [':' + name], 49 | main = main, 50 | tester = tester, 51 | tester_args = tester_args) 52 | 53 | def clj_module(name,src=None,modules=[],main=None,tests=[],test_modules=[],resources=[],test_resources=[]): 54 | """First is CLJ wrapper - nothing fancy, we just predefine what 55 | project file to use, setup tester (which is an only options for 56 | CLJ for now) and add Clojure as dependency that is added always. 57 | """ 58 | module('clj', 59 | 'project-clj', 60 | name, 61 | src, 62 | main, 63 | resources, 64 | ensure_list(modules) + [ext('org.clojure/clojure')], 65 | tests, 66 | test_resources, 67 | test_modules, 68 | executer('tester-lein-clj')) 69 | 70 | def cljs_module(name,src=None,modules=[],main=None,tests=[],test_modules=[],resources=[],test_resources=[],itests=[]): 71 | """Here goes CLJS wrapper with example of custom logic - if tests 72 | supplied than planck test executer is used (because it's 10 times 73 | faster!), otherwise classic doo. Another custom logic is release 74 | task - if main is specified then new target is added which creates 75 | release bundle 76 | """ 77 | module('cljs', 78 | 'project-cljs', 79 | name, 80 | src, 81 | main, 82 | resources, 83 | ensure_list(modules) + cljs_deps, 84 | ensure_list(tests) + ensure_list(itests), 85 | test_resources, 86 | test_modules, 87 | executer('tester-lein-cljs-doo') if itests else executer('tester-lein-cljs-planck')) 88 | 89 | if (main): 90 | genrule(name + '-release', 91 | srcs = [], 92 | bash = 'mkdir $OUT && cd $(location :{0}) && lein cljsbuild once release && cp release/{0}.js $OUT && cp -r resources $OUT'.format(name), 93 | out = 'build') 94 | 95 | def cljc_module(name,src=None,modules=[],main=None,tests=[],test_modules=[],resources=[],test_resources=[],itests=[]): 96 | module('cljc', 97 | 'project-cljs', 98 | name, 99 | src, 100 | main, 101 | resources, 102 | ensure_list(modules) + cljs_deps, 103 | ensure_list(tests) + ensure_list(itests), 104 | test_resources, 105 | test_modules, 106 | executer('tester-lein-cljs-doo') if itests else executer('tester-lein-cljs-planck')) 107 | 108 | -------------------------------------------------------------------------------- /clj-cljs-config/figwheel-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /clj-cljs-config/project-clj.clj: -------------------------------------------------------------------------------- 1 | (defproject {{name}} "0.0.1" 2 | :dependencies [{{deps}}] 3 | :main {{main}} 4 | :aot [{{main}}] 5 | :source-paths ["src" "test"]) 6 | -------------------------------------------------------------------------------- /clj-cljs-config/project-cljs.clj: -------------------------------------------------------------------------------- 1 | (defproject {{name}} "0.0.1" 2 | :dependencies [{{deps}}] 3 | :plugins [[lein-cljsbuild "1.1.3"] 4 | [lein-doo "0.1.6"]] 5 | :source-paths ["src" "test"] 6 | :cljsbuild {:builds {:debug {:source-paths ["src" "test"] 7 | :compiler {:optimizations :whitespace 8 | :parallel-build true 9 | :language-in :ecmascript5 10 | :language-out :ecmascript5 11 | :output-dir "target" 12 | :output-to "target/{{name}}.js"}} 13 | :release {:source-paths ["src"] 14 | :compiler {:optimizations :advanced 15 | :language-in :ecmascript5 16 | :language-out :ecmascript5 17 | :output-dir "release" 18 | :output-to "release/{{name}}.js"}}}}) 19 | -------------------------------------------------------------------------------- /clj-cljs-config/project-repl.clj: -------------------------------------------------------------------------------- 1 | (defproject repl "0.0.1" 2 | :dependencies [{{deps}}] 3 | :plugins [[lein-cljsbuild "1.1.3"] 4 | [lein-figwheel "0.5.4-7"]] 5 | :source-paths ["src"] 6 | :repl-options {:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl] 7 | :init (do (use 'figwheel-sidecar.repl-api)(start-figwheel!))} 8 | :figwheel {:hawk-options {:watcher :polling}} 9 | :cljsbuild {:builds {:repl {:source-paths ["src"] 10 | :figwheel true 11 | :compiler {:language-in :ecmascript5 12 | :language-out :ecmascript5 13 | :main module.core 14 | :asset-path "js" 15 | :output-dir "resources/public/js" 16 | :output-to "resources/public/js/module.js"}}}}) 17 | -------------------------------------------------------------------------------- /lib.py: -------------------------------------------------------------------------------- 1 | def clj_cljs_module(ext, # One of [clj cljs cljc] 2 | project_file, # Buck query that points to project file associated with extension 3 | builder, # Buck query to builder which would be run for building a module 4 | name, # Name of generated target. Following targets will be created ['target' '__target' 'target-test'] 5 | src = None, # List of source files that module uses 6 | modules = [], # List of submodules that current module depends on. It will be copied to module folder before building 7 | resources = [], # List of resources that current module depends on. It will be copied to resource module folder before building 8 | main = None, # Main entry point of a module if exists. Used for releases 9 | tester = None, # If current module is a test then it should be a Buck query to tester which would be run for testing a module 10 | tester_args = []): # List of additional test arguments which would be supplied to tester during test run 11 | 12 | # Prepares required resources and modules sub-dependencies. Create 13 | # info file with module settings and calls builder 14 | genrule(name = '__' + name if tester else name, 15 | srcs = src, 16 | bash = 'mkdir -p $OUT/resources && ' + 17 | ('&&'.join(map(lambda d: 'rsync -r $(location ' + d + ') $OUT/resources',resources)) if len(resources) else 'true') + '&&' + 18 | ('&&'.join(map(lambda d: 'rsync -r --prune-empty-dirs $(location ' + d + ')/resources/ $OUT/resources',modules)) if len(modules) else 'true') + '&&' + 19 | 'echo "{name};{type};{main};$SRCDIR;$OUT;" > $OUT/info && '.format(name=name,type=ext,main=main or "") + 20 | ('&&'.join(map(lambda d: 'echo "$(location ' + d + ')" >> $OUT/deps',modules)) if len(modules) else 'true') + '&& ' + 21 | 'cp $(location {0}) $OUT/project.clj && '.format(project_file) + 22 | '$(location {0}) $OUT/info {1}'.format(builder,'test' if tester else 'build'), 23 | out = 'build', 24 | visibility = ['PUBLIC']) 25 | 26 | if tester: # Actual test task - simply run tester in the right folder 27 | sh_test(name = name + '-test', 28 | test = tester, 29 | args = ['$(location :{0})'.format('__' + name)] + tester_args, 30 | deps = [':__' + name]) 31 | 32 | def ext_dep(name): 33 | """Defines external dependency which then can be references from other 34 | modules. Name should be in format '[name] [version]' like 'koh 35 | 0.1.1', but it will generate a target with name equal to name 36 | without version, ex. 'koh'. Idea is to have only one version of 37 | external dependency across your monorepo while other modules don't 38 | know anything about actual version""" 39 | genrule(name = name.split()[0], 40 | srcs = [], 41 | bash = 'mkdir -p $OUT/{{src,resources}} && echo "[{0} \\"{1}\\"]" > $OUT/deps'.format(*name.split()), 42 | out = 'build', 43 | visibility = ['PUBLIC']) 44 | 45 | def clj_cljs_repl(name,builder,project_file,resource,output_folder): 46 | genrule(name = name, 47 | srcs = [], 48 | bash = 'echo "$(location {0}) repl-init $(location {0}) $(location {1}) $(location {2}) {3} \$@" > $OUT && chmod +x $OUT'.format(builder,project_file,resource,output_folder), 49 | out = 'repl.sh', 50 | visibility = ['PUBLIC'], 51 | executable = True) 52 | -------------------------------------------------------------------------------- /tester-lein-clj.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | cd $1 && LEIN_ROOT=1 lein test 4 | -------------------------------------------------------------------------------- /tester-lein-cljc-doo.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | cd $1 4 | 5 | echo "(ns test.runner (:require [doo.runner :refer-macros [doo-all-tests]] \ 6 | [module.core])) (doo-all-tests)" > test/runner.cljs 7 | LEIN_ROOT=1 lein test && lein doo phantom debug once 8 | -------------------------------------------------------------------------------- /tester-lein-cljc-planck.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | cd $1 4 | 5 | lein test 6 | 7 | planck --classpath="test:src" \ 8 | --eval="(ns test.test (:require [module.core] [cljs.test] [planck.core]))" \ 9 | --eval="(defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m] (when-not (cljs.test/successful? m) (planck.core/exit 1)))" \ 10 | --eval="(cljs.test/run-all-tests)" 11 | -------------------------------------------------------------------------------- /tester-lein-cljs-doo.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | cd $1 4 | 5 | echo "(ns test.runner (:require [doo.runner :refer-macros [doo-all-tests]] \ 6 | [module.core])) (doo-all-tests)" > test/runner.cljs 7 | LEIN_ROOT=1 lein doo phantom debug once 8 | -------------------------------------------------------------------------------- /tester-lein-cljs-planck.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | cd $1 4 | 5 | planck --classpath="test:src" \ 6 | --eval="(ns test.test (:require [module.core] [cljs.test] [planck.core]))" \ 7 | --eval="(defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m] (when-not (cljs.test/successful? m) (planck.core/exit 1)))" \ 8 | --eval="(cljs.test/run-all-tests)" 9 | -------------------------------------------------------------------------------- /tests/BUCK.tests: -------------------------------------------------------------------------------- 1 | # Here we define all external dependencies for CLJ/CLJS. Right way 2 | # would be storing all the external sources in the monorepo but it's a 3 | # shortcut: With maven and clojars releases are meant to be immutable, 4 | # so we are safe. Just make sure you are not using SNAPSHOT version 5 | # which breaks the whole idea. For testing purposes it's inside one 6 | # file, but I recommend to create some folder like ext and put 7 | # dependencies there, so it will be easy to notice that `//ext:ktoa` 8 | # is external module 9 | 10 | for dep in [# Default dependencies 11 | 'org.clojure/clojure 1.8.0', 12 | 'org.clojure/clojurescript 1.9.89', 13 | # CLJS specifics 14 | 'figwheel-sidecar 0.5.4-7', 15 | 'com.cemerick/piggieback 0.2.1', 16 | 'lein-doo 0.1.6', 17 | # For testing 18 | 'org.clojure/data.json 0.2.6', 19 | 'aleph 0.4.1-beta2', 20 | 'org.omcljs/om 1.0.0-alpha28']: 21 | ext_dep(dep) 22 | 23 | #Simplest 24 | cljs_module('a-cljs') 25 | clj_module('a-clj') 26 | cljc_module('a-cljc') 27 | 28 | # Modules 29 | cljs_module('b-cljs', modules = ':a-cljs') 30 | clj_module('b-clj', modules = ':a-clj') 31 | cljc_module('b-cljc', modules = ':a-cljc') 32 | 33 | # Deps 34 | cljs_module('c-cljs', modules = ':org.clojure/data.json') 35 | clj_module('c-clj', modules = ':org.clojure/data.json') 36 | cljc_module('c-cljc', modules = ':org.clojure/data.json') 37 | 38 | # Src 39 | cljs_module('d-cljs', src = ['d1.cljs','d2.cljs']) 40 | clj_module('d-clj', src = ['d1.clj','d2.clj']) 41 | cljc_module('d-cljc', src = ['d1.cljc','d2.cljc']) 42 | 43 | # Tests 44 | cljs_module('e-cljs', tests = 'e_test.cljs') 45 | clj_module('e-clj', tests = 'e_test.clj') 46 | cljc_module('e-cljc', tests = 'e_test.cljc') 47 | 48 | # Main 49 | cljs_module('f-cljs', main = 'f.core.f-cljs') 50 | clj_module('f-clj', main = 'f.core.f-clj') 51 | cljc_module('f-cljc', main = 'f.core.f-cljc') 52 | 53 | # Advanced 54 | cljs_module('g-cljs', 55 | modules = [':a-cljs', 56 | ':b-cljs', 57 | ':c-cljs', 58 | ':d-cljs', 59 | ':e-cljc', 60 | ':org.clojure/data.json', 61 | ':aleph', 62 | ':org.omcljs/om'], 63 | tests = ['g1_test.cljs','g2_test.cljs'], 64 | main = 'g.core.g-cljs') 65 | 66 | clj_module('g-clj', 67 | modules = [':a-clj', 68 | ':b-clj', 69 | ':c-clj', 70 | ':d-clj', 71 | ':e-cljc', 72 | ':org.clojure/data.json', 73 | ':aleph'], 74 | tests = ['g1_test.clj','g2_test.clj'], 75 | main = 'g.core.g-clj') 76 | 77 | cljc_module('g-cljc', 78 | modules = [':a-cljc', 79 | ':b-cljc', 80 | ':c-cljc', 81 | ':d-cljc', 82 | ':e-cljc', 83 | ':org.clojure/data.json', 84 | ':aleph', 85 | ':org.omcljs/om'], 86 | tests = ['g1_test.cljc','g2_test.cljc'], 87 | main = 'g.core.g-cljc') 88 | -------------------------------------------------------------------------------- /tests/a_clj.clj: -------------------------------------------------------------------------------- 1 | (ns a.a-clj) 2 | 3 | (def a "a") 4 | -------------------------------------------------------------------------------- /tests/a_cljc.cljc: -------------------------------------------------------------------------------- 1 | (ns a.a-cljc) 2 | 3 | (def a "a") 4 | -------------------------------------------------------------------------------- /tests/a_cljs.cljs: -------------------------------------------------------------------------------- 1 | (ns a.a-cljs) 2 | 3 | (def a "a") 4 | -------------------------------------------------------------------------------- /tests/b.cljs: -------------------------------------------------------------------------------- 1 | (ns example.b) 2 | -------------------------------------------------------------------------------- /tests/b_clj.clj: -------------------------------------------------------------------------------- 1 | (ns b.b-clj 2 | (:require [a.a-clj :refer [a]])) 3 | 4 | (def b (str "b" a)) 5 | -------------------------------------------------------------------------------- /tests/b_cljc.cljc: -------------------------------------------------------------------------------- 1 | (ns b.b-cljc 2 | (:require [a.a-cljc :refer [a]])) 3 | 4 | (def b (str "b" a)) 5 | -------------------------------------------------------------------------------- /tests/b_cljs.cljs: -------------------------------------------------------------------------------- 1 | (ns b.b-cljs 2 | (:require [a.a-cljs :refer [a]])) 3 | 4 | (def b (str "b" a)) 5 | -------------------------------------------------------------------------------- /tests/c.cljc: -------------------------------------------------------------------------------- 1 | (ns example.c) 2 | -------------------------------------------------------------------------------- /tests/c_clj.clj: -------------------------------------------------------------------------------- 1 | (ns c.c-clj 2 | (:require [clojure.data.json :as json])) 3 | 4 | (def c "c") 5 | -------------------------------------------------------------------------------- /tests/c_cljc.cljc: -------------------------------------------------------------------------------- 1 | (ns c.c-cljc 2 | #?(:clj (:require [clojure.data.json :as json]))) 3 | 4 | (def c "c") 5 | -------------------------------------------------------------------------------- /tests/c_cljs.cljs: -------------------------------------------------------------------------------- 1 | (ns c.c-cljs) 2 | 3 | (def c "c") 4 | -------------------------------------------------------------------------------- /tests/d1.clj: -------------------------------------------------------------------------------- 1 | (ns d.d1) 2 | 3 | (def d1 "d1") 4 | -------------------------------------------------------------------------------- /tests/d1.cljc: -------------------------------------------------------------------------------- 1 | (ns d.d1) 2 | 3 | (def d1 "d1") 4 | -------------------------------------------------------------------------------- /tests/d1.cljs: -------------------------------------------------------------------------------- 1 | (ns d.d1) 2 | 3 | (def d1 "d1") 4 | -------------------------------------------------------------------------------- /tests/d2.clj: -------------------------------------------------------------------------------- 1 | (ns d.d2) 2 | 3 | (def d2 "d2") 4 | -------------------------------------------------------------------------------- /tests/d2.cljc: -------------------------------------------------------------------------------- 1 | (ns d.d2) 2 | 3 | (def d2 "d2") 4 | -------------------------------------------------------------------------------- /tests/d2.cljs: -------------------------------------------------------------------------------- 1 | (ns d.d2) 2 | 3 | (def d2 "d2") 4 | -------------------------------------------------------------------------------- /tests/e_clj.clj: -------------------------------------------------------------------------------- 1 | (ns e.e-clj) 2 | 3 | (def e "e") 4 | -------------------------------------------------------------------------------- /tests/e_cljc.cljc: -------------------------------------------------------------------------------- 1 | (ns e.e-cljc) 2 | 3 | (def e "e") 4 | -------------------------------------------------------------------------------- /tests/e_cljs.cljs: -------------------------------------------------------------------------------- 1 | (ns e.e-cljs) 2 | 3 | (def e "e") 4 | -------------------------------------------------------------------------------- /tests/e_test.clj: -------------------------------------------------------------------------------- 1 | (ns e.e-test 2 | (:require [e.e-clj :refer [e]] 3 | [clojure.test :refer [is deftest]])) 4 | 5 | (deftest e-test 6 | (is (= e "e"))) 7 | -------------------------------------------------------------------------------- /tests/e_test.cljc: -------------------------------------------------------------------------------- 1 | (ns e.e-test 2 | #?(:clj (:require [e.e-cljc :refer [e]] 3 | [clojure.test :refer [is deftest]])) 4 | #?(:cljs (:require [e.e-cljc :refer [e]] 5 | [cljs.test :refer-macros [deftest is testing]]))) 6 | 7 | (deftest e-test 8 | (is (= e "e"))) 9 | -------------------------------------------------------------------------------- /tests/e_test.cljs: -------------------------------------------------------------------------------- 1 | (ns e.e-test 2 | (:require [e.e-cljs :refer [e]] 3 | [cljs.test :refer-macros [deftest is testing]])) 4 | 5 | (deftest e-test 6 | (is (= e "e"))) 7 | -------------------------------------------------------------------------------- /tests/f_clj.clj: -------------------------------------------------------------------------------- 1 | (ns f.core.f-clj 2 | (:gen-class)) 3 | 4 | (defn -main [& args] 5 | (println "f")) 6 | -------------------------------------------------------------------------------- /tests/f_cljc.cljc: -------------------------------------------------------------------------------- 1 | (ns f.core.f-cljc 2 | #?(:clj (:gen-class))) 3 | 4 | #?(:clj 5 | (defn -main [& args] 6 | (println "f"))) 7 | 8 | #?(:cljs 9 | (.log js/console "f")) 10 | -------------------------------------------------------------------------------- /tests/f_cljs.cljs: -------------------------------------------------------------------------------- 1 | (ns f.core.f-cljs) 2 | 3 | (.log js/console "f") 4 | -------------------------------------------------------------------------------- /tests/g1_test.clj: -------------------------------------------------------------------------------- 1 | (ns g.g1-test 2 | (:require [g.core.g-clj :refer [g]] 3 | [clojure.test :refer [is deftest]])) 4 | 5 | (deftest g-g1-test 6 | (is (= g "abacd1d2e"))) 7 | -------------------------------------------------------------------------------- /tests/g1_test.cljc: -------------------------------------------------------------------------------- 1 | (ns g.g1-test 2 | #?(:clj (:require [g.core.g-cljc :refer [g]] 3 | [clojure.test :refer [is deftest]])) 4 | #?(:cljs (:require [g.core.g-cljc :refer [g]] 5 | [cljs.test :refer-macros [deftest is testing]]))) 6 | 7 | (deftest g-g1-test 8 | (is (= g "abacd1d2e"))) 9 | -------------------------------------------------------------------------------- /tests/g1_test.cljs: -------------------------------------------------------------------------------- 1 | (ns g.g1-test 2 | (:require [g.core.g-cljs :refer [g]] 3 | [cljs.test :refer-macros [deftest is testing]])) 4 | 5 | (deftest g-g1-test 6 | (is (= g "abacd1d2e"))) 7 | -------------------------------------------------------------------------------- /tests/g2_test.clj: -------------------------------------------------------------------------------- 1 | (ns g.g2-test 2 | (:require [g.core.g-clj :refer [g]] 3 | [clojure.test :refer [is deftest]])) 4 | 5 | (deftest g-2-test 6 | (is (= g "abacd1d2e"))) 7 | -------------------------------------------------------------------------------- /tests/g2_test.cljc: -------------------------------------------------------------------------------- 1 | (ns g.g2-test 2 | #?(:clj (:require [g.core.g-cljc :refer [g]] 3 | [clojure.test :refer [is deftest]])) 4 | #?(:cljs (:require [g.core.g-cljc :refer [g]] 5 | [cljs.test :refer-macros [deftest is testing]]))) 6 | 7 | (deftest g-g2-test 8 | (is (= g "abacd1d2e"))) 9 | -------------------------------------------------------------------------------- /tests/g2_test.cljs: -------------------------------------------------------------------------------- 1 | (ns g.g2-test 2 | (:require [g.core.g-cljs :refer [g]] 3 | [cljs.test :refer-macros [deftest is testing]])) 4 | 5 | (deftest g-g2-test 6 | (is (= g "abacd1d2e"))) 7 | -------------------------------------------------------------------------------- /tests/g_clj.clj: -------------------------------------------------------------------------------- 1 | (ns g.core.g-clj 2 | (:require [a.a-clj :refer [a]] 3 | [b.b-clj :refer [b]] 4 | [c.c-clj :refer [c]] 5 | [d.d1 :refer [d1]] 6 | [d.d2 :refer [d2]] 7 | [e.e-cljc :refer [e]] 8 | [aleph.http :as http] 9 | [clojure.data.json :as json]) 10 | (:gen-class)) 11 | 12 | (def g (str a b c d1 d2 e)) 13 | -------------------------------------------------------------------------------- /tests/g_cljc.cljc: -------------------------------------------------------------------------------- 1 | (ns g.core.g-cljc 2 | (:require [a.a-cljc :refer [a]] 3 | [b.b-cljc :refer [b]] 4 | [c.c-cljc :refer [c]] 5 | [d.d1 :refer [d1]] 6 | [d.d2 :refer [d2]] 7 | [e.e-cljc :refer [e]]) 8 | #?(:clj (:require [clojure.test :refer [deftest is run-tests]] 9 | [aleph.http :as http])) 10 | #?(:clj (:gen-class))) 11 | 12 | (def g (str a b c d1 d2 e)) 13 | -------------------------------------------------------------------------------- /tests/g_cljs.cljs: -------------------------------------------------------------------------------- 1 | (ns g.core.g-cljs 2 | (:require [a.a-cljs :refer [a]] 3 | [b.b-cljs :refer [b]] 4 | [c.c-cljs :refer [c]] 5 | [d.d1 :refer [d1]] 6 | [d.d2 :refer [d2]] 7 | [e.e-cljc :refer [e]])) 8 | 9 | (def g (str a b c d1 d2 e)) 10 | -------------------------------------------------------------------------------- /tests/gen-out.sh: -------------------------------------------------------------------------------- 1 | # Run from the root of the repo, like: 2 | # ./RULES/clojure-clojurescript-buck/tests/gen-out.sh `buck targets RULES/clojure-clojurescript-buck/tests:` 3 | 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 5 | out=$DIR/output 6 | doc=$out/tests.md 7 | 8 | rm -rf $out/* 9 | echo "# Test output" > $doc 10 | 11 | for target in "$@" 12 | do 13 | name=`echo $target | sed -e "s/.*://"` 14 | echo "## $name" >> $doc 15 | buck query "deps('$target')" --dot | sed -e "s/\/\/RULES\/clojure-clojurescript-buck\/tests//g; s/\/\/RULES\/clojure-clojurescript-buck//g" | dot -Tpng > $out/$name.png 16 | echo '!'"[$name]($name.png)" >> $doc 17 | build=`echo $target | sed -e "s/\//buck-out\/gen/; s/:/\//"` 18 | echo '```' >> $doc 19 | tree $build >> $doc 20 | echo '```' >> $doc 21 | 22 | if [ $(find $build -name "project.clj" | wc -l) -gt 0 ]; then 23 | echo '`cat project.clj`:' >> $doc 24 | echo '``` clojure' >> $doc 25 | find $build -name "project.clj" -exec cat {} \; >> $doc 26 | echo '```' >> $doc 27 | fi 28 | done 29 | --------------------------------------------------------------------------------