-
-
Notifications
You must be signed in to change notification settings - Fork 103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use as a Build System #110
Comments
I can't find anything about building with NPM... I suppose you want shards to become aware of targets to build, downloading dependencies, building the targets, and maybe installing them? It may be a little out of scope, at least for the time being. More often than not a simple Makefile is usually enough. What would be your use cases? And what kind of problems should it be solving? I don't know much about Cargo's building features nor Maven's, and I only skimmed rebar or the Haskell build tool (Cabal?). |
I think it becomes interesting to define a In that scenario, having different options for different targets (like cross-compilation, compiler or linker options) become necessary. However I feel that Crystal still lacks a bit of maturity in relation to cross compilation (ie, it always uses |
I could see adding the ability to define arbitrary groups as being useful. Something akin to a Gemfile in ruby. Then a makefile etc.. could build the executable the appropriate groups enabled. I only gave the shards yml SPEC a cursory glance, but it appears to only allow dependencies and development_dependencies. I could see the possible need for test, production etc... groups as well, but there is no need to limit it to just those. It would be neat if any type of group could be named in the shards yml. precompile, assets, docker etc.. There are many possibilities. |
The more I think about it, the more I like the idea. I'm not saying I will work on this anytime soon, but I believe this is a good development for Crystal. An example use case (thinking out loud). Crystal's source could follow the Git scheme and build multiple binaries like targets:
crystal-build:
main: src/crystal/commands/build.cr
crystal-docs:
main: src/crystal/commands/docs.cr Then:
|
I was just about to submit this same request. One language that I love is Typescript which has a file called This would make a world of difference with the vscode plugin which, right now, throws all kinds of errors when attempting to work on We would no longer need Makefiles! |
Here is what my brain came up with as an example name: crystal
version: 0.19.4
description: The Crystal Programming Language http://crystal-lang.org
authors:
- Ary Borenszweig <aborenszweig@manas.com.ar>
- Juan Wajnerman <waj@manas.tech>
license: Apache
targets:
build:
crystal: .build/crystal
main: src/compiler/crystal.cr
flags:
- without_openssl
- without_zlib |
Now available in master. |
Actually, I think that Crystal is quite good at cross compilation, thanks to LLVM. At least it's easy to build an object file for whatever OS/arch combo. No need to deal with different compilers for different targets, just specify The problem is linking the generated object file. It's complex, and sometimes impossible (eg: link a macOS binary from Linux). It requires a toolchain and libraries for the target architecture, that must be specified with the |
The The following features could be useful:
For example: targets:
obj/local/armeabi/foo.so:
main: src/foo.cr
args: --target=arm-unknown-linux-androideabi
link: toolchains/armeabi/bin/clang --sysroot=toolchains/armeabi/sysroot -Lobj/local/armeabi
obj/local/armeabi-v7a/foo.so:
main: src/foo.cr
args: --target=arm-unknown-linux-androideabi --mattr=+armv7-a+vfp3
link: toolchains/armeabi/bin/clang --sysroot=toolchains/armeabi/sysroot -Lobj/local/armeabi-v7a
obj/local/arm64-v8a/foo.so:
main: src/foo.cr
args: --target=aarch64-unknown-linux-android
link: toolchains/arm64-v8a/bin/clang --sysroot=toolchains/arm64-v8a/sysroot -Lobj/local/arm64-v8a |
I am missing some stuff. In my first Crystal project I need to use makefiles to manage compilation.
"scripts": {
"test": "mocha --harmony --check-leaks",
"travis:test": "npm run cover -- --report lcovonly",
"travis:lint": "npm run lint-files && npm run nsp",
"appveyor": "node --max_old_space_size=4096 node_modules\\mocha\\bin\\mocha --harmony",
"build:examples": "cd examples && node buildAll.js",
"pretest": "npm run lint-files",
"lint-files": "npm run lint && npm run beautify-lint",
"lint": "eslint lib bin hot",
"beautify-lint": "beautify-lint 'lib/**/*.js' 'hot/**/*.js' 'bin/**/*.js' 'benchmark/*.js' 'test/*.js'",
"nsp": "nsp check --output summary",
"cover": "node --harmony ./node_modules/.bin/istanbul cover -x '**/*.runtime.js' node_modules/mocha/bin/_mocha",
"publish-patch": "npm run lint && npm run beautify-lint && mocha && npm version patch && git push && git push --tags && npm publish"
} Then you can use npm run <script name>
don't mind me, opinion is like ass everyone has one. :p |
In my opinion, using makefiles instead of pushing the complexity to shards is the correct solution. Why reinvent the wheel when make works so well? |
Yes, please use a regular Makefile to run arbitrary commands with dependencies. It's simpler and doesn't have to be reimplemented. |
The problem with Makefiles is that they leaks project dependencies to environment. When moving project to new box/developer I need to have additional tooling and platform specific install script for Makefile dependencies. I would love if crystal projects are self-contained. This would make docker images easier to create and easier to have cross-platform projects and will not force people to learn make... Ideally you should only clone project, run shards to install deps and start build with watch->compile. This is typical workflow in npm projects. For example: https://github.com/mxstbr/react-boilerplate I am not necessary suggesting adding arbitrary shell scripting like npm, I feel it is a bit messy. |
Sorry, I want to avoid duplicating functionalities that are standardised, already available on all platforms and most likely already installed in developer boxes, or just a small package away. |
what is standardised way to watch for file changes? I am using watchman from facebook. |
I find |
In my view, combining build tool and dependency resolution has lead to lots of scope creep for say Maven in the Java world. Keeping them separate might avoid that. |
Please keep it simple, do one thing and do it well! Cargo is horribly designed, it’s everything-but-kitchen-sink bloatware. And the worst about it is that you need Cargo and Rust to build Cargo and you need Cargo to build Rust, so double chicken-or-egg problem with bootstrapping. Please, please, do not make the same mistake! It’s better to keep it as separate tools and just call each other as external commands. I’m author and maintainer of Rust (and Crystal) package in Alpine Linux. First I was really excited from Rust and wanted to bring it to Alpine and spent a lot of time on it. This experience from the distribution PoV eventually completely changed my relation to Rust. Now I’m very frustrated from Rust, even thinking about giving it up (maintaining rust/cargo package), mostly because of Cargo. Maven and npm are even worse examples for inspiration. |
@jirutka I totally agree about having one job and doing it well, but i'm not familiar with rust and cargo. What mistakes do rust/cargo make which hurt distro packagers, so that we don't accidentally make them? Shards has already resisted becoming a system package manager (it only ever manages packages inside the current directory, no |
So... I've seen some people recommend using makefiles, but what is the recommended way to handle Crystal dependencies in makefiles? This is the best I've come up with: .PHONY: all libs
PROGRAM = do_something
LIBS = lib/one_library lib/another_one
SOURCE_FILES := $(shell find source/ -type f)
LIB_FILES := $(foreach dir,$(LIBS),$(shell find $(dir) -type f 2> /dev/null))
all: $(PROGRAM)
$(PROGRAM): %: source/%.cr $(SOURCE_FILES) $(LIBS) $(LIB_FILES)
crystal build -o $@ $<
# The phony libs target should only be run if the dependencies need
# to be installed - otherwise, the program will always be rebuilt.
ifneq ($(shell crystal deps check > /dev/null 2> /dev/null; echo $$?),0)
$(LIBS): libs
endif
libs:
crystal deps install It feels hacky, but I can't come up with a way to dynamically get the dependencies from |
I'd do something like: CRYSTAL_FILES := $(shell find src lib -type f -name '*.cr)
bin/my_program: $(CRYSTAL_FILES) lib
crystal build -o $@ src/entrypoint.cr
lib: shards.yml shards.lock
shards install
touch lib That's a pretty robust makefile because the dependency isn't on "crystal deps check returns 1" it's on whether shards.yml or shards.lock have been updated. And yes, you can depend on directories in make, as long as you ensure their mtime is updated. You might not even need the |
Oh, that's not too bad. Thanks. Using that solution, it won't rebuild if you remove any of the libraries without removing the |
@obskyr if you delete anything in the |
crystal-lang/crystal#11481 might eventually provide a way to discover true dependencies, including e.g. non-source files loaded from macros, and emit depfiles. Until then the A drawback here is that # unix-like
GLOB = $(shell find $1 -type f -name $2)
# windows
GLOB = $(shell dir $1\\$2 /B /S)
SOURCES := $(call GLOB,src,*.cr)
LIB_SOURCES := $(call GLOB,lib,*.cr) |
I use this |
Some minimal amount of C-compiler step support would be helpful to make non-shared-object C-bindings more accessible as shards. Some examples from tree-sitter/tree-sitter-javascript: Rust has a fn build() {
// Paths are relative to `Cargo.toml` by default (i.e. `shard.yml` for Crystal)
let mut c_config = cc::Build::new();
c_config.std("c11").include("src");
c_config.file("src/parser.c")
c_config.file("src/scanner.c");
c_config.compile("tree-sitter-javascript");
} While Go offers an alternate package tree_sitter_javascript
// #cgo CFLAGS: -std=c11 -fPIC
// #include "../../src/parser.c"
// #include "../../src/scanner.c"
import "C" Hypothetical in crystal: @[CC(std: "c11", include: "#{__DIR__}/../../src/parser.c, #{__DIR__}/../../src/scanner.c")]
lib LibTreeSitterJavascript
# ... define extern types as usual ...
end |
At the very least I think targets:
target_one:
main: src/main1.cr
flags:
- preview_mt
target_two:
main: src/main2.cr
flags:
- my_custom_flag
- preview_mt The only other workaround is tooling-specific stuff which can be cumbersome for newcomers and is generally not ideal. For example, for the vscode extension I need to resort to flags being set in the workspace settings, which need to apply to every target in your workspace (which can contain multiple projects), and your |
Should
shards
be used as a build system manager in the future - much like Rust'scargo
, node.js'snpm
, or Java'smaven
?The text was updated successfully, but these errors were encountered: