artemyarulin / clojure-clojurescript-buck

Set of macroses for Buck build system that allows building Clojure and ClojureScript
MIT License
23 stars 0 forks source link

Rethink key principles again #22

Closed artemyarulin closed 8 years ago

artemyarulin commented 8 years ago

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:

Let's take some hammock and solve all of those

artemyarulin commented 8 years ago
artemyarulin commented 8 years ago

Getting back to testing deps: We could solve by making test target a module as well: Meaning test target is just a module with their own dependencies where one of them is a code under tests. Here how it may look:

def clj_cljs_module(ext,project_file,builder,tester,name,src=None,modules=[],main=None,tests=[],test_modules=[],tester_args=[],resources=[],isTest=False):
    src = [name.replace('-','_') + '.' + ext] if src == None else ensure_list(src)
    modules,tests,resources= map(ensure_list,[modules,tests,resources])

    genrule(name = name,
            srcs = src,
            bash = 'mkdir -p $OUT/resources && ' +
                   ('&&'.join(map(lambda d: 'rsync -r $(location ' + d + ') $OUT/resources',resources)) if len(resources) else 'true') + '&&' +
                   ('&&'.join(map(lambda d: 'rsync -r --prune-empty-dirs $(location ' + d + ')/resources/ $OUT/resources',modules)) if len(modules) else 'true') + '&&' +
                   'echo "{name};{type};{main};$SRCDIR;$OUT;" > $OUT/info && '.format(name=name,type=ext,main=main or "") +
                   ('&&'.join(map(lambda d: 'echo "$(location ' + d + ')" >> $OUT/deps',modules)) if len(modules) else 'true') + '&& ' +
                   '$(location {0}) $OUT/info {1}'.format(builder,'test' if isTest else 'build'),
            out = 'build',
            visibility = ['PUBLIC'])

    if tests:
        clj_cljs_module(ext, project_file, builder, tester, '__' + name, src = tests, modules = test_modules, tester_args = tester_args, isTest = True)

    if isTest:
      sh_test(name = name,
              test = tester,
              args = ['$(location :{0})'.format('__' + name)] + ensure_list(tester_args),
              deps = [':__' + name])
artemyarulin commented 8 years ago

REPL implementation idea

Create one buck executable which will accept a list of targets as an argument. After run it will create a new folder (where? In /tmp I guess as buck-out in under control of Buck itself) and sym-link all the source files into that (we can find it using previously mentioned buck query "filter('clj.?$',labels(srcs,'//...'))"). As we cannot simply link it and we have to put it in a right place we pre-process each file by executable reusing the same code from build.cljs. There should be no problem with tests as we don't care if it's in the same src folder with actual source.

Issues

Use cases

Considering following example command: buck run //RULES/clj-cljs-config:repl -- //[target] Using Buck aliases like:

[alias]
  repl     = //RULES/clj-cljs-config:repl

we can simplify it till buck run repl -- //target which is nice. As don't want to block Buck the command itself will only build sym-links and take care about project.clj file creation without running actual REPL. We can (not sure yet) move to the project directory, so that we can do buck run repl -- //[target] && lein repl.

Here 3 use cases how REPL may be used:

Configuration

We should follow the same approach like with whole project where configuration comes through the user config. Here home the it may look:

# RULES/clj-cljs-config/BUCK

clj_cljs_repl(name = 'repl', # Will create //RULES/clj-cljs-config:repl target. In case user may want to have multiple REPL profiles
              project_file = ':project-repl', # Just a target path to file, same as with project files
              output_folder = '.repl') # Relative from Buck root or absolute path where REPL folder would be created

Risks

artemyarulin commented 8 years ago

Answering risks:

Using hard links like ln repo/target/source.clj repl-folder/src/target/soruce.clj and using pooling mechanism for Figwheel like

:figwheel  {:hawk-options {:watcher :polling}}

makes live reloading happen. Soft links ln -s and default FS events based file watcher doesn't work.

Creating aliases works fine for Buck runnable as well.

As we cannot use soft links it means that whenever original file got deleted or renamed our REPL folder would still have a copy of it. The only thing we can do is to actually implement sync process which would go though all the files. Or as a shortcut - delete all the files in src folder and copy all the files into that folder again.

artemyarulin commented 8 years ago

Just to keep in mind: I was thinking that buck run repl --buck query "//..."`` would be the most popular choice for REPL but it wouldn't often work:

artemyarulin commented 8 years ago

Another problem is dependencies, while getting names is easy filter(ext,deps(//[target])) we don't have an access to version numbers.

One workaround could be: catbuck targets "//[target]" --show-output --verbose 0 | awk '{print $2}'/deps, but in this case we suppose that target is built already which could be too big assumption.

Better workaround could be building all the ext_dep files and getting exact version from it like:

buck build "//ext:" && catbuck targets "//ext:" --show-output --verbose 0 | awk '{print $2 "/deps"}'``

artemyarulin commented 8 years ago

OK, it's not possible to track resources with this approach.

We have to find another way. Question, why some files goes to src and others goes to resources?

$> buck query "labels(srcs,deps(//lib/react-native/react-native-externs:))"
# Everything here should be under src, even though it's not CLJ/CLJS files
lib/react-native/react-native-externs/src/core.cljs
lib/react-native/react-native-externs/src/deps.cljs
lib/react-native/react-native-externs/src/react/react.ext.js
lib/react-native/react-native-externs/src/react/react.native.ext.js

$> buck query "labels(srcs,deps(//kapteko/blog:blog))"
# All non CLJ/CLJS files should be under resources
kapteko/blog/blog.cljs
kapteko/blog/articles.clj
kapteko/blog/articles/2016-01-10-10-39:Hello_world!.md
artemyarulin commented 8 years ago

OK, it's not possible to have this approach in general: We are running buck commands (queries) while being inside command (buck run) which not stable with v2016.03.28.01 and doesn't work at all with further versions.

I still want to have buck run repl --buck query "//..."` as it's very convenient entry point for REPL tasks. One possible approach could be using the same principle but rather than executing a command (which will fail) we can output the command to execute, so that Buck finishes it's own part of generating a string and then we eval this string outside of Buck. In our example it could be something like that:$(buck run repl -- buck query "//...")`

artemyarulin commented 8 years ago

So, it tuns out it's a valid approach, it works fine, couple of comments:

1 As we didn't found a way to distinguish resource from source file we decided to copy non CLJ/CLJS files into both src and resources. One of my targets has index.html which got copied into src folder and it seems that Figwheel searching src folders first for index.html files, which pretty much breaks it.

2 It turns out errors like js/require is not defined or any other runtime errors doesn't cause Figwheel to stop, so it works just fine in this case

3 it's slow - for our whole monorepo it takes 2:34 in order to re-sync changes if "//..." query is used. It could be dramatically improved if we concat multiple queries into one

4 Code it ugly so far

Overall it's good result - finally we have a working approach which needs some polishing.

artemyarulin commented 8 years ago

Closing it as there is nothing left to rethink - yes we have some hardcoded things, some things are slow, but it's all the matter of other issues