Giter VIP home page Giter VIP logo

clojure-clojurescript-buck's Introduction

Clojure-ClojureScript-Buck

Build Status

Clojure and ClojureScript support for Buck build system. If you have Clojure/ClojureScript and monorepo then it's a thing to check

Features

  • Build Clojure/ClojureScript
  • Run tests for Clojure and choose what test runner to use with ClojureScript: doo or Planck!
  • Build and test any Clojure/ClojureScript with always the same command: buck build [module-name] && buck test [module-name]-test
  • Run REPL into the module without too much thinking about details: buck run [module-name]-repl
  • All the features from Buck build system - it's a peace of cake!

What problems does it solve

  • It allows you to use Clojure/ClojureScript inside a monorepo where different projects may depends on each other
  • 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
  • Buck allows to abstract build/test steps into functions that can be used later on across your repo

Installation and requirements

Currently only MacOS is supported. Linux/Windows support is covered in this issue 18

  • Put content of this repo to the right place in your monorepo
  • Rename clj-cljs-config/BUCK.example to clj-cljs-config/BUCK to make it processable by Buck
  • Change .buckconfig so that it will include your config file by default:
[buildfile]
  includes = //path/to-config/clj-cljs-config/config.py

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

Configuration

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 where you can specify custom project files, default dependencies (Clojure/ClojureScript versions), different builders or testers

How does it work

Under the hood we simply create Leiningen project and put files and parameters in the right place.

Entry point would be your custom wrapper with supplied project file on top of clj_cljs_module function in 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 is available.

builder then:

  • Create a normal folder structure (with source files placed in a right sub-folder, etc.)
  • Collect all sub-dependencies
  • Create entry point file which requires all the existing module namespaces (including tests) which simplifies REPL and testing
  • Update project file with actual data

Status

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

Alternatives

  • 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.

  • Ladder developer mentioned on HN 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.

  • make - there are no tasks that you cannot do with make. If you like bare metal - then check version 1.0.0, it was implemented with power of shell,sed,grep and regexps.

clojure-clojurescript-buck's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

clojure-clojurescript-buck's Issues

Think about planck again

So, we have many ugly reg-exp, which is not a problem by itself - it works quiet stable. But here the list of bigger problems:

  • Fighwheel requires to have main in the project file. Specifying it manually for each library requires to much extra work, but without it we have no REPL
  • We have custom ./build.sh repl [target] command which we heavily use in order to open NREPL connection. It's outside of this repo, while with Clojure it's essential thing to have. And the implementation with Clojure is straight forward, while with ClojureScript we have additional watchman/rsync madness
  • Even with ./build.sh repl it still requires to much thinking what target to choose, as currently we have like ~5 targets per library?
  • With buck exec we can add more cool commands to targets - generate docs, release to clojars, etc.
  • Too many targets created for each module
  • Customisation of project.clj
  • Add test-deps property - it quiet usual case that we want to have additional modules for test
  • Sometimes we need to define multiple test runners - node|browser|rn or jvm. Can we unify it as well?
  • By default we only test with :whitespace optimisations. How user can check that module works with advanced optimisations as well?
  • Consider case: I have a module which works on node, tests passes. I've added it as a dependency to the module which is browser only. As we don't copy the tests from the dependencies we may not notice such mistake
  • Consider extracting task like clj_dep so that modules would use always modules functionality. This way we'll able to lock external dependencies versions. Although it would make releasing open source projects much harder

So, what we can do - unify things by using buck executables and handle all parsing with planck

Test deps

We should be able to specify module dependencies for tests only - currently it's not possible. It shouldn't be that difficult as we have a dedicated target for tests

Sort our targets

Currently we have only one build target that builds tests as well.

If we build dependencies graph it would look like module depends on actual dependencies plus test dependencies, while test dependencies is needed only for testing.

So, the end result (including #6) should look like:
Build targets:

  • :example.src - creates project, gathers modules, inject dependencies into project file, outputs source files
  • :example.build - takes :example.src as an input and outputs either class files or js file with simple optimizations
  • :example.build_tests - takes :example.src and merges there test files and test dependencies, then builds the project without running

If main specified additionally:

  • :example.release- takes :example.src as an input and outputs either uberjar or js file with advanced optimisations

Test targets:

  • :example.test

Two additional macros should be created clj_test and cljs_test. We should continue support tests/test_modules/test_dependencies in clj(s)_module as it's useful shortcut and covers the most often case. Although it should be possible to created additional test targets for the cases like integration test, performance, etc.

Example:

clj_module(name = 'integration',
           target = ':example.src',
           src = ['integration_test.clj'])

which additionally should produce build target :example.build_test_integration and test target :example.test_integration

Alternative way could be to support multiple test targets inside clj(s)_module, something like:

clj_module('example',
           test_modules = ['//test-helper/a','//test-helper/b'],
           test_deps = ['[some-test-dep "0.0.1"'],
           tests = {'unit':['unit_test.clj'],
                    'integration':['integration_test.clj']})

Explain customisation

Most probably developer would like to change project templates, what is the right way of doing it? What other things they may want to change?

Multiple test runners

It's easy to notice that for CLJC we have test runners that basically is a copy of CLJ and CLJS test runners merged together. Such duplication is not good.

Another issue - right now I want to integrate CLJ/CLJS code linters. The only way to accomplish it right now by changing all test runners, which far away from being a good thing to do.

What we should be able to do is to define test runners (linter is just one kind of tests) and be able to compose it together.

Main issue comes from the way how we run test runners:

    if tester: # Actual test task - simply run tester in the right folder
        sh_test(name = name + '-test',
                test = tester,
                args = ['$(location :{0})'.format('__' + name)] + tester_args,
                deps = [':__' + name])

As you can see Buck expects test runner to be an executable, so that's why we have all those tester*.sh in the project.

We cannot do anything, but dynamically create new test runners.

Essentially we could implement something like that:

# config.py
test_runner_clj = 'LEIN_ROOT=1 lein test`
test_runner_cljs_doo = """
echo "(ns test.runner (:require [doo.runner :refer-macros [doo-all-tests]] \
                                [module.core])) (doo-all-tests)" > test/runner.cljs
LEIN_ROOT=1 lein doo phantom debug once
"""
test_runner_cljs_planck = """
planck --classpath="test:src" \
       --eval="(ns test.test (:require [module.core] [cljs.test] [planck.core]))" \
       --eval="(defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m] (when-not (cljs.test/successful? m) (planck.core/exit 1)))" \
       --eval="(cljs.test/run-all-tests)"
"""
linter = "lein kibit"

# Will create //RULES/clj-cljs-config:test-runner-clj
test_runner(name='test-runner-clj',
            commands = [linter,test_runner_clj])

# Will create //RULES/clj-cljs-config:test-runner-cljs-planck
test_runner(name='test-runner-cljs_planck'
            commands = [linter,test_runner_cljs_planck])

# Will create //RULES/clj-cljs-config:test-runner-cljc-planck
test_runner(name='test-runner-cljc_planck',
            commands = [linter,test_runner_clj,test_runner_cljs_planck])

clj_cljs_module(...,test_runner = ':test-runner-cljc-planck',....)

Linux/Windows support

Due to the fact that we are using Planck under the hood only MacOS is supported for now.

There is a work in progress of writing Planck 2.0 using C so it will work on Linux machines as well (actually beta version is available already)

Another solution could be rewriting build.cljs to use simple CLJS and run it with NodeJS. It's not a big deal, maybe some IO functions gonna behave differently, but nothing dramatically.

As I'm currently using heavily ReactNative and MacOS is needed in any case I'll postpone with solution for this problem.

If you are interesting in this, need some support or if it's a show stopper for you - feel free to ping me, we will figure it out

Figwheel support

REPL is nice, but with Buck we broke usual Figwheel workflow - saving file doesn't do anything now as we are serving REPL from a different folder with source copies.

We cannot simply rerun build as it will clean up output folder first so all JS files gonna disappear.

Previously we had some fragile script which looks something like that:

project_dir=`buck build $2 && buck targets $2 --show-output | awk '{print $2}'`
tmp=`mktemp -d`
rsync -r $project_dir/ $tmp/
watchman-make -p 'lib/**/*' 'RULES/**/*' --make "buck build $2 && rsync -r $PWD/$project_dir/ $tmp/" -t '' & (cd $tmp && lein repl) && kill $!

repl fails with NSPOSIXErrorDomain error 24

If I run $(buck run repl -- //...) to process everything in my monorepo eventually planck will stop with error:

The operation couldn\U2019t be completed. (NSPOSIXErrorDomain error 24 - Too many open files)

Rethink key principles again

Although current approach works rather well (absolutely custom cljs_rn_module is a good proof of that) we still have couple of issues which we should address:

  • Figwheel approach isn's possible (and any other tools for live reload) as source files are copied to the other destination and no in place editing is possible (no auto-reload then). One approach is described #19
  • buck run [target]-repl blocks other Buck tasks from running (plus sometimes it leaves running REPL behind after closing)
  • Test targets should support additional modules and resources. Issue #21 (it's possible by creating new separate target)
  • As Buck encourages to have many small independent modules REPL development became harder: Running and switching to different REPL session, for different modules is not convenient.

Let's take some hammock and solve all of those

Preparation for 1.0 release

For each clj(s)_module declaration we should automatically define following targets:

  • :[name] - outputs /src/, /tests/, /project.clj. Test shouldn't contain dependencies tests
  • :release - output /app.jar OR /app.js
  • :test - run tests

As we have to parse Clojure bash is not reliable for this purpose. We gonna move all the parsing to Planck, because it allows doing things really simple:

(require 'cljs.tools.reader)

(nth (first ((cljs.tools.reader/read-string (core/slurp "buck-out/gen/clojure/yieldm/project/project.yieldm/project.clj"))) 4)
# [[aleph "0.4.1-beta2"] [org.clojure/clojure "1.8.0"] [org.clojure/tools.namespace "0.3.0-alpha3"]]

Awesome thing about Planck - it allows to call shell commands. Although it's not available on Linux, so should we actually go with Clojure then?

Clojure Test issues

  • Dash has to be converted to the underscore in the same way like with Clojure source
  • Test should be placed in a right place (meaning folder should be created for each part of a NS)
  • Check - but looks like no project modification has to be made: No need to include test folder to src
  • Create test buck target using sh_test

Add motivation speech

We had it once at the beginning of the README but it was too loudly and a bit of out of the scope of this project. It is still true but maybe it make sense to create a separate markdown file somewhere and add a link to it from the README?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.