crystalruby 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/Dockerfile +23 -2
- data/README.md +395 -198
- data/Rakefile +4 -3
- data/crystalruby.gemspec +2 -2
- data/examples/adder/adder.rb +1 -1
- data/exe/crystalruby +1 -0
- data/lib/crystalruby/adapter.rb +143 -73
- data/lib/crystalruby/arc_mutex.rb +47 -0
- data/lib/crystalruby/compilation.rb +32 -3
- data/lib/crystalruby/config.rb +41 -37
- data/lib/crystalruby/function.rb +216 -73
- data/lib/crystalruby/library.rb +157 -51
- data/lib/crystalruby/reactor.rb +63 -44
- data/lib/crystalruby/source_reader.rb +92 -0
- data/lib/crystalruby/template.rb +16 -5
- data/lib/crystalruby/templates/function.cr +11 -10
- data/lib/crystalruby/templates/index.cr +53 -66
- data/lib/crystalruby/templates/inline_chunk.cr +1 -1
- data/lib/crystalruby/templates/ruby_interface.cr +34 -0
- data/lib/crystalruby/templates/top_level_function.cr +62 -0
- data/lib/crystalruby/templates/top_level_ruby_interface.cr +33 -0
- data/lib/crystalruby/typebuilder.rb +11 -55
- data/lib/crystalruby/typemaps.rb +92 -67
- data/lib/crystalruby/types/concerns/allocator.rb +80 -0
- data/lib/crystalruby/types/fixed_width/named_tuple.cr +80 -0
- data/lib/crystalruby/types/fixed_width/named_tuple.rb +86 -0
- data/lib/crystalruby/types/fixed_width/proc.cr +45 -0
- data/lib/crystalruby/types/fixed_width/proc.rb +79 -0
- data/lib/crystalruby/types/fixed_width/tagged_union.cr +53 -0
- data/lib/crystalruby/types/fixed_width/tagged_union.rb +113 -0
- data/lib/crystalruby/types/fixed_width/tuple.cr +82 -0
- data/lib/crystalruby/types/fixed_width/tuple.rb +92 -0
- data/lib/crystalruby/types/fixed_width.cr +138 -0
- data/lib/crystalruby/types/fixed_width.rb +205 -0
- data/lib/crystalruby/types/primitive.cr +21 -0
- data/lib/crystalruby/types/primitive.rb +117 -0
- data/lib/crystalruby/types/primitive_types/bool.cr +34 -0
- data/lib/crystalruby/types/primitive_types/bool.rb +11 -0
- data/lib/crystalruby/types/primitive_types/nil.cr +35 -0
- data/lib/crystalruby/types/primitive_types/nil.rb +16 -0
- data/lib/crystalruby/types/primitive_types/numbers.cr +37 -0
- data/lib/crystalruby/types/primitive_types/numbers.rb +28 -0
- data/lib/crystalruby/types/primitive_types/symbol.cr +55 -0
- data/lib/crystalruby/types/primitive_types/symbol.rb +35 -0
- data/lib/crystalruby/types/primitive_types/time.cr +35 -0
- data/lib/crystalruby/types/primitive_types/time.rb +25 -0
- data/lib/crystalruby/types/type.cr +64 -0
- data/lib/crystalruby/types/type.rb +249 -30
- data/lib/crystalruby/types/variable_width/array.cr +74 -0
- data/lib/crystalruby/types/variable_width/array.rb +88 -0
- data/lib/crystalruby/types/variable_width/hash.cr +146 -0
- data/lib/crystalruby/types/variable_width/hash.rb +117 -0
- data/lib/crystalruby/types/variable_width/string.cr +36 -0
- data/lib/crystalruby/types/variable_width/string.rb +18 -0
- data/lib/crystalruby/types/variable_width.cr +23 -0
- data/lib/crystalruby/types/variable_width.rb +46 -0
- data/lib/crystalruby/types.rb +32 -13
- data/lib/crystalruby/version.rb +2 -2
- data/lib/crystalruby.rb +13 -6
- metadata +42 -22
- data/lib/crystalruby/types/array.rb +0 -15
- data/lib/crystalruby/types/bool.rb +0 -3
- data/lib/crystalruby/types/hash.rb +0 -17
- data/lib/crystalruby/types/named_tuple.rb +0 -28
- data/lib/crystalruby/types/nil.rb +0 -3
- data/lib/crystalruby/types/numbers.rb +0 -5
- data/lib/crystalruby/types/string.rb +0 -3
- data/lib/crystalruby/types/symbol.rb +0 -3
- data/lib/crystalruby/types/time.rb +0 -8
- data/lib/crystalruby/types/tuple.rb +0 -17
- data/lib/crystalruby/types/type_serializer/json.rb +0 -41
- data/lib/crystalruby/types/type_serializer.rb +0 -37
- data/lib/crystalruby/types/typedef.rb +0 -57
- data/lib/crystalruby/types/union_type.rb +0 -43
- data/lib/module.rb +0 -3
data/Rakefile
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "bundler/gem_tasks"
|
4
|
-
require "minitest/test_task"
|
5
|
-
|
6
|
-
Minitest::TestTask.create
|
7
4
|
|
8
5
|
require "rubocop/rake_task"
|
9
6
|
|
10
7
|
RuboCop::RakeTask.new
|
11
8
|
|
12
9
|
task default: %i[test rubocop]
|
10
|
+
|
11
|
+
task :test do
|
12
|
+
require_relative "test/test_all"
|
13
|
+
end
|
data/crystalruby.gemspec
CHANGED
@@ -4,7 +4,7 @@ require_relative "lib/crystalruby/version"
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "crystalruby"
|
7
|
-
spec.version =
|
7
|
+
spec.version = CrystalRuby::VERSION
|
8
8
|
spec.authors = ["Wouter Coppieters"]
|
9
9
|
spec.email = ["wc@pico.net.nz"]
|
10
10
|
|
@@ -35,7 +35,7 @@ Gem::Specification.new do |spec|
|
|
35
35
|
spec.add_dependency "digest"
|
36
36
|
spec.add_dependency "ffi"
|
37
37
|
spec.add_dependency "fileutils"
|
38
|
-
spec.add_dependency "
|
38
|
+
spec.add_dependency "prism"
|
39
39
|
# For more information and examples about making a new gem, check out our
|
40
40
|
# guide at: https://bundler.io/guides/creating_gem.html
|
41
41
|
end
|
data/examples/adder/adder.rb
CHANGED
data/exe/crystalruby
CHANGED
data/lib/crystalruby/adapter.rb
CHANGED
@@ -1,23 +1,23 @@
|
|
1
1
|
module CrystalRuby
|
2
2
|
module Adapter
|
3
|
-
# Use this method to annotate a Ruby method that should be
|
3
|
+
# Use this method to annotate a Ruby method that should be crystallized.
|
4
4
|
# Compilation and attachment of the method is done lazily.
|
5
5
|
# You can force compilation by calling `CrystalRuby.compile!`
|
6
|
-
# It's important that all code using
|
6
|
+
# It's important that all code using crystallized methods is
|
7
7
|
# loaded before any manual calls to compile.
|
8
8
|
#
|
9
9
|
# E.g.
|
10
10
|
#
|
11
|
-
#
|
12
|
-
# def add(a, b)
|
11
|
+
# crystallize :int32
|
12
|
+
# def add(a: :int32, b: :int32)
|
13
13
|
# a + b
|
14
14
|
# end
|
15
15
|
#
|
16
16
|
# Pass `raw: true` to pass Raw crystal code to the compiler as a string instead.
|
17
17
|
# (Useful for cases where the Crystal method body is not valid Ruby)
|
18
18
|
# E.g.
|
19
|
-
#
|
20
|
-
# def add(a, b)
|
19
|
+
# crystallize :int32, raw: true
|
20
|
+
# def add(a: :int32, b: :int32)
|
21
21
|
# <<~CRYSTAL
|
22
22
|
# a + b
|
23
23
|
# CRYSTAL
|
@@ -30,104 +30,174 @@ module CrystalRuby
|
|
30
30
|
#
|
31
31
|
# Pass lib: "name_of_lib" to compile Crystal code into several distinct libraries.
|
32
32
|
# This can help keep compilation times low, by packaging your Crystal code into separate shared objects.
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
# This method provides a useful DSL for defining Crystal types in pure Ruby.
|
49
|
-
# These types can not be passed directly to Ruby, and must be serialized as either:
|
50
|
-
# JSON or
|
51
|
-
# C-Structures (WIP)
|
52
|
-
#
|
53
|
-
# See #json for an example of how to define arguments or return types for complex objects.
|
54
|
-
# E.g.
|
55
|
-
#
|
56
|
-
# MyType = crtype{ Int32 | Hash(String, Array(Bool) | Float65 | Nil) }
|
57
|
-
def crtype(&block)
|
58
|
-
TypeBuilder.with_injected_type_dsl(self) do
|
59
|
-
TypeBuilder.build(&block)
|
33
|
+
# @param returns The return type of the method. Optional (defaults to :void).
|
34
|
+
# @param [Hash] options The options hash.
|
35
|
+
# @option options [Boolean] :raw (false) Pass raw Crystal code to the compiler as a string.
|
36
|
+
# @option options [Boolean] :async (false) Mark the method as async (allows multiplexing).
|
37
|
+
# @option options [String] :lib ("crystalruby") The name of the library to compile the Crystal code into.
|
38
|
+
# @option options [Proc] :block An optional wrapper Ruby block that wraps around any invocations of the crystal code
|
39
|
+
def crystallize( returns=:void, raw: false, async: false, lib: "crystalruby", &block)
|
40
|
+
(self == TOPLEVEL_BINDING.receiver ? Object : self).instance_eval do
|
41
|
+
@crystallize_next = {
|
42
|
+
raw: raw,
|
43
|
+
async: async,
|
44
|
+
returns: returns,
|
45
|
+
block: block,
|
46
|
+
lib: lib
|
47
|
+
}
|
60
48
|
end
|
61
49
|
end
|
62
50
|
|
63
|
-
#
|
64
|
-
|
65
|
-
#
|
66
|
-
# E.g.
|
67
|
-
# crystalize [a: json{ Int32 | Float64 | Nil } ] => NamedStruct(result: Int32 | Float64 | Nil)
|
68
|
-
def json(&block)
|
69
|
-
crtype(&block).serialize_as(:json)
|
70
|
-
end
|
51
|
+
# Alias for `crystallize`
|
52
|
+
alias :crystalize :crystallize
|
71
53
|
|
72
|
-
#
|
73
|
-
#
|
74
|
-
|
75
|
-
|
76
|
-
|
54
|
+
# Exposes a Ruby method to one or more Crystal libraries.
|
55
|
+
# Type annotations follow the same rules as the `crystallize` method, but are
|
56
|
+
# applied in reverse.
|
57
|
+
# @param returns The return type of the method. Optional (defaults to :void).
|
58
|
+
# @param [Hash] options The options hash.
|
59
|
+
# @option options [Boolean] :raw (false) Pass raw Crystal code to the compiler as a string.
|
60
|
+
# @option options [String] :libs (["crystalruby"]) The name of the Crystal librarie(s) to expose the Ruby code to.
|
61
|
+
def expose_to_crystal( returns=:void, libs: ["crystalruby"])
|
62
|
+
(self == TOPLEVEL_BINDING.receiver ? Object : self).instance_eval do
|
63
|
+
@expose_next_to_crystal = {
|
64
|
+
returns: returns,
|
65
|
+
libs: libs
|
66
|
+
}
|
67
|
+
end
|
77
68
|
end
|
78
69
|
|
79
|
-
#
|
80
|
-
#
|
81
|
-
|
82
|
-
|
83
|
-
|
70
|
+
# Define a shard dependency
|
71
|
+
# This dependency will be automatically injected into the shard.yml file for
|
72
|
+
# the given library and installed upon compile if it is not already installed.
|
73
|
+
def shard(shard_name, lib: 'crystalruby', **opts)
|
74
|
+
CrystalRuby::Library[lib].require_shard(shard_name, **opts)
|
84
75
|
end
|
85
76
|
|
86
77
|
# Use this method to define inline Crystal code that does not need to be bound to a Ruby method.
|
87
78
|
# This is useful for defining classes, modules, performing set-up tasks etc.
|
88
|
-
# See: docs for .
|
79
|
+
# See: docs for .crystallize to understand the `raw` and `lib` parameters.
|
89
80
|
def crystal(raw: false, lib: "crystalruby", &block)
|
90
|
-
inline_crystal_body = Template::InlineChunk.render(
|
81
|
+
inline_crystal_body = respond_to?(:name) ? Template::InlineChunk.render(
|
91
82
|
{
|
92
|
-
module_name: name,
|
93
|
-
|
94
|
-
|
95
|
-
|
83
|
+
module_name: name,
|
84
|
+
body: SourceReader.extract_source_from_proc(block, raw: raw),
|
85
|
+
mod_or_class: self.kind_of?(Class) && self < Types::Type ? "class" : "module",
|
86
|
+
superclass: self.kind_of?(Class) && self < Types::Type ? "< #{self.crystal_supertype}" : ""
|
87
|
+
}) :
|
88
|
+
SourceReader.extract_source_from_proc(block, raw: raw)
|
89
|
+
|
90
|
+
CrystalRuby::Library[lib].crystallize_chunk(
|
96
91
|
self,
|
97
92
|
Digest::MD5.hexdigest(inline_crystal_body),
|
98
93
|
inline_crystal_body
|
99
94
|
)
|
100
95
|
end
|
101
96
|
|
102
|
-
|
97
|
+
|
98
|
+
# This method provides a useful DSL for defining Crystal types in pure Ruby
|
99
|
+
# MyType = CRType{ Int32 | Hash(String, Array(Bool) | Float65 | Nil) }
|
100
|
+
# @param [Proc] block The block within which we build the type definition.
|
101
|
+
def CRType(&block)
|
102
|
+
TypeBuilder.build_from_source(block, context: self)
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
# We trigger attaching of crystallized instance methods here.
|
108
|
+
# If a method is added after a crystallize annotation we assume it's the target of the crystallize annotation.
|
109
|
+
# @param [Symbol] method_name The name of the method being added.
|
110
|
+
def method_added(method_name)
|
111
|
+
define_crystallized_method(instance_method(method_name)) if should_crystallize_next?
|
112
|
+
expose_ruby_method_to_crystal(instance_method(method_name)) if should_expose_next?
|
113
|
+
super
|
114
|
+
end
|
115
|
+
|
116
|
+
# We trigger attaching of crystallized class methods here.
|
117
|
+
# If a method is added after a crystallize annotation we assume it's the target of the crystallize annotation.
|
118
|
+
# @note This method is called when a method is added to the singleton class of the object.
|
119
|
+
# @param [Symbol] method_name The name of the method being added.
|
120
|
+
def singleton_method_added(method_name)
|
121
|
+
define_crystallized_method(singleton_method(method_name)) if should_crystallize_next?
|
122
|
+
expose_ruby_method_to_crystal(singleton_method(method_name)) if should_expose_next?
|
123
|
+
super
|
124
|
+
end
|
125
|
+
|
126
|
+
# Helper method to determine if the next method added should be crystallized.
|
127
|
+
# @return [Boolean] True if the next method added should be crystallized.
|
128
|
+
def should_crystallize_next?
|
129
|
+
defined?(@crystallize_next) && @crystallize_next
|
130
|
+
end
|
131
|
+
|
132
|
+
# Helper method to determine if the next method added should be exposed to Crystal libraries.
|
133
|
+
# @return [Boolean] True if the next method added should be exposed.
|
134
|
+
def should_expose_next?
|
135
|
+
defined?(@expose_next_to_crystal) && @expose_next_to_crystal
|
136
|
+
end
|
137
|
+
|
138
|
+
# This is where we extract the Ruby method metadata and invoke the Crystal::Library functionality
|
139
|
+
# to compile a stub for the Ruby method into the Crystal library.
|
140
|
+
def expose_ruby_method_to_crystal(method)
|
141
|
+
returns, libs = @expose_next_to_crystal.values_at(:returns, :libs)
|
142
|
+
@expose_next_to_crystal = nil
|
143
|
+
|
144
|
+
args, source = SourceReader.extract_args_and_source_from_method(method)
|
145
|
+
returns = args.delete(:returns) if args[:returns] && returns == :void
|
146
|
+
args[:__yield_to] = args.delete(:yield) if args[:yield]
|
147
|
+
src = <<~RUBY
|
148
|
+
def #{method.name} (#{(args.keys - [:__yield_to]).join(", ")})
|
149
|
+
#{source}
|
150
|
+
end
|
151
|
+
RUBY
|
152
|
+
|
153
|
+
owner = method.owner.singleton_class? ? method.owner.attached_object : method.owner
|
154
|
+
owner.class_eval(src)
|
155
|
+
owner.instance_eval(src) unless method.kind_of?(UnboundMethod) && method.owner.ancestors.include?(CrystalRuby::Types::Type)
|
156
|
+
method = owner.send(method.kind_of?(UnboundMethod) ? :instance_method : :method, method.name)
|
157
|
+
|
158
|
+
libs.each do |lib|
|
159
|
+
CrystalRuby::Library[lib].expose_method(
|
160
|
+
method,
|
161
|
+
args,
|
162
|
+
returns,
|
163
|
+
)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# We attach crystallized class methods here.
|
103
168
|
# This function is responsible for
|
104
169
|
# - Generating the Crystal source code
|
105
170
|
# - Overwriting the method and class methods by the same name in the caller.
|
106
171
|
# - Lazily triggering compilation and attachment of the Ruby method to the Crystal code.
|
107
172
|
# - We also optionally prepend a block (if given) to the owner, to allow Ruby code to wrap around Crystal code.
|
108
|
-
|
109
|
-
|
173
|
+
# @param [Symbol] method_name The name of the method being added.
|
174
|
+
# @param [UnboundMethod] method The method being added.
|
175
|
+
def define_crystallized_method(method)
|
176
|
+
CrystalRuby.log_debug("Defining crystallized method #{name}.#{method.name}")
|
177
|
+
|
178
|
+
returns, block, async, lib, raw = @crystallize_next.values_at(:returns, :block, :async, :lib, :raw)
|
179
|
+
@crystallize_next = nil
|
180
|
+
|
181
|
+
args, source = SourceReader.extract_args_and_source_from_method(method, raw: raw)
|
110
182
|
|
111
|
-
|
112
|
-
|
183
|
+
# We can safely claim the `yield` argument name for typing the yielded block
|
184
|
+
# because this is an illegal identifier in Crystal anyway.
|
185
|
+
args[:__yield_to] = args.delete(:yield) if args[:yield]
|
113
186
|
|
114
|
-
|
187
|
+
returns = args.delete(:returns) if args[:returns] && returns == :void
|
188
|
+
|
189
|
+
CrystalRuby::Library[lib].crystallize_method(
|
115
190
|
method,
|
116
191
|
args,
|
117
192
|
returns,
|
118
|
-
|
193
|
+
source,
|
119
194
|
async,
|
120
195
|
&block
|
121
196
|
)
|
122
197
|
end
|
123
|
-
|
124
|
-
# Extract Ruby source to serve as Crystal code directly.
|
125
|
-
# If it's a raw method, we'll strip the string delimiters at either end of the definition.
|
126
|
-
# We need to clear the MethodSource cache here to allow for code reloading.
|
127
|
-
def extract_source(method_or_block, raw: false)
|
128
|
-
method_or_block.source.lines[raw ? 2...-2 : 1...-1].join("\n").tap do
|
129
|
-
MethodSource.instance_variable_get(:@lines_for_file).delete(method_or_block.source_location[0])
|
130
|
-
end
|
131
|
-
end
|
132
198
|
end
|
133
199
|
end
|
200
|
+
|
201
|
+
Module.prepend(CrystalRuby::Adapter)
|
202
|
+
BasicObject.prepend(CrystalRuby::Adapter)
|
203
|
+
BasicObject.singleton_class.prepend(CrystalRuby::Adapter)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module CrystalRuby
|
2
|
+
module LibC
|
3
|
+
extend FFI::Library
|
4
|
+
ffi_lib "c"
|
5
|
+
class PThreadMutexT < FFI::Struct
|
6
|
+
layout :__align, :int64, :__size, :char, 40
|
7
|
+
end
|
8
|
+
|
9
|
+
attach_function :pthread_mutex_init, [PThreadMutexT.by_ref, :pointer], :int
|
10
|
+
attach_function :pthread_mutex_lock, [PThreadMutexT.by_ref], :int
|
11
|
+
attach_function :pthread_mutex_unlock, [PThreadMutexT.by_ref], :int
|
12
|
+
end
|
13
|
+
|
14
|
+
class ArcMutex
|
15
|
+
def phtread_mutex
|
16
|
+
@phtread_mutex ||= init_mutex!
|
17
|
+
end
|
18
|
+
|
19
|
+
def synchronize
|
20
|
+
lock
|
21
|
+
yield
|
22
|
+
unlock
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_ptr
|
26
|
+
phtread_mutex.pointer
|
27
|
+
end
|
28
|
+
|
29
|
+
def init_mutex!
|
30
|
+
mutex = LibC::PThreadMutexT.new
|
31
|
+
res = LibC.pthread_mutex_init(mutex, nil)
|
32
|
+
raise "Failed to initialize mutex" unless res.zero?
|
33
|
+
|
34
|
+
mutex
|
35
|
+
end
|
36
|
+
|
37
|
+
def lock
|
38
|
+
res = LibC.pthread_mutex_lock(phtread_mutex)
|
39
|
+
raise "Failed to lock mutex" unless res.zero?
|
40
|
+
end
|
41
|
+
|
42
|
+
def unlock
|
43
|
+
res = LibC.pthread_mutex_unlock(phtread_mutex)
|
44
|
+
raise "Failed to unlock mutex" unless res.zero?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -1,10 +1,18 @@
|
|
1
1
|
require "tmpdir"
|
2
2
|
require "shellwords"
|
3
|
+
require "timeout"
|
3
4
|
|
4
5
|
module CrystalRuby
|
5
6
|
module Compilation
|
6
7
|
class CompilationFailedError < StandardError; end
|
7
8
|
|
9
|
+
# Simple wrapper around invocation of the Crystal compiler
|
10
|
+
# @param src [String] path to the source file
|
11
|
+
# @param lib [String] path to the library file
|
12
|
+
# @param verbose [Boolean] whether to print the compiler output
|
13
|
+
# @param debug [Boolean] whether to compile in debug mode
|
14
|
+
# @raise [CompilationFailedError] if the compilation fails
|
15
|
+
# @return [void]
|
8
16
|
def self.compile!(
|
9
17
|
src:,
|
10
18
|
lib:,
|
@@ -13,15 +21,36 @@ module CrystalRuby
|
|
13
21
|
)
|
14
22
|
compile_command = build_compile_command(verbose: verbose, debug: debug, lib: lib, src: src)
|
15
23
|
CrystalRuby.log_debug "Compiling Crystal code #{verbose ? ": #{compile_command}" : ""}"
|
16
|
-
|
24
|
+
IO.popen(compile_command, chdir: File.dirname(src), &:read)
|
25
|
+
raise CompilationFailedError, "Compilation failed in #{src}" unless $?&.success?
|
17
26
|
end
|
18
27
|
|
28
|
+
# Builds the command to compile the Crystal source file
|
29
|
+
# @param verbose [Boolean] whether to print the compiler output
|
30
|
+
# @param debug [Boolean] whether to compile in debug mode
|
31
|
+
# @param lib [String] path to the library file
|
32
|
+
# @param src [String] path to the source file
|
33
|
+
# @return [String] the command to compile the Crystal source file
|
19
34
|
def self.build_compile_command(verbose:, debug:, lib:, src:)
|
20
35
|
verbose_flag = verbose ? "--verbose --progress" : ""
|
21
36
|
debug_flag = debug ? "" : "--release --no-debug"
|
22
37
|
redirect_output = " > /dev/null " unless verbose
|
23
|
-
lib, src
|
24
|
-
%(
|
38
|
+
lib, src = [lib, src].map(&Shellwords.method(:escape))
|
39
|
+
%(crystal build #{verbose_flag} #{debug_flag} --single-module --link-flags "-shared" -o #{lib} #{src}#{redirect_output})
|
40
|
+
end
|
41
|
+
|
42
|
+
# Trigger the shards install command in the given source directory
|
43
|
+
def self.install_shards!(src_dir)
|
44
|
+
CrystalRuby.log_debug "Running shards install inside #{src_dir}"
|
45
|
+
output = IO.popen("shards update", chdir: src_dir, &:read)
|
46
|
+
CrystalRuby.log_debug output if CrystalRuby.config.verbose
|
47
|
+
raise CompilationFailedError, "Shards install failed" unless $?&.success?
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return whether the shards check command succeeds in the given source directory
|
51
|
+
def self.shard_check?(src_dir)
|
52
|
+
IO.popen("shards check", chdir: src_dir, &:read)
|
53
|
+
$?&.success?
|
25
54
|
end
|
26
55
|
end
|
27
56
|
end
|
data/lib/crystalruby/config.rb
CHANGED
@@ -17,59 +17,63 @@ module CrystalRuby
|
|
17
17
|
# - CrystalRuby.configure block
|
18
18
|
class Configuration
|
19
19
|
include Singleton
|
20
|
-
attr_accessor :debug, :verbose, :logger, :colorize_log_output,
|
20
|
+
attr_accessor :debug, :verbose, :logger, :colorize_log_output,
|
21
|
+
:single_thread_mode, :crystal_missing_ignore
|
21
22
|
|
22
23
|
def initialize
|
23
|
-
@debug = true
|
24
24
|
@paths_cache = {}
|
25
|
-
config =
|
25
|
+
config = read_config || {}
|
26
|
+
@crystal_src_dir = config.fetch("crystal_src_dir", "./crystalruby")
|
27
|
+
@crystal_codegen_dir = config.fetch("crystal_codegen_dir", "generated")
|
28
|
+
@crystal_project_root = config.fetch("crystal_project_root", Pathname.pwd)
|
29
|
+
@crystal_missing_ignore = config.fetch("crystal_missing_ignore", false)
|
30
|
+
@debug = config.fetch("debug", false)
|
31
|
+
@verbose = config.fetch("verbose", false)
|
32
|
+
@single_thread_mode = config.fetch("single_thread_mode", false)
|
33
|
+
@colorize_log_output = config.fetch("colorize_log_output", false)
|
34
|
+
@log_level = config.fetch("log_level", ENV.fetch("CRYSTALRUBY_LOG_LEVEL", "info"))
|
35
|
+
@logger = Logger.new($stdout)
|
36
|
+
@logger.level = Logger.const_get(@log_level.to_s.upcase)
|
37
|
+
end
|
38
|
+
|
39
|
+
def file_config
|
40
|
+
@file_config ||= File.exist?("crystalruby.yaml") && begin
|
26
41
|
YAML.safe_load(IO.read("crystalruby.yaml"))
|
27
42
|
rescue StandardError
|
28
43
|
nil
|
29
44
|
end || {}
|
30
|
-
@crystal_src_dir = config.fetch("crystal_src_dir", "./crystalruby")
|
31
|
-
@crystal_codegen_dir = config.fetch("crystal_codegen_dir", "generated")
|
32
|
-
@crystal_project_root = config.fetch("crystal_project_root", Pathname.pwd)
|
33
|
-
@debug = config.fetch("debug", true)
|
34
|
-
@verbose = config.fetch("verbose", false)
|
35
|
-
@single_thread_mode = config.fetch("single_thread_mode", false)
|
36
|
-
@colorize_log_output = config.fetch("colorize_log_output", false)
|
37
|
-
@log_level = config.fetch("log_level", ENV.fetch("CRYSTALRUBY_LOG_LEVEL", "info"))
|
38
|
-
@logger = Logger.new(STDOUT)
|
39
|
-
@logger.level = Logger.const_get(@log_level.to_s.upcase)
|
40
45
|
end
|
41
46
|
|
42
|
-
|
43
|
-
|
44
|
-
|
47
|
+
def self.define_directory_accessors!(parent, directory_node)
|
48
|
+
case directory_node
|
49
|
+
when Array then directory_node.each(&method(:define_directory_accessors!).curry[parent])
|
50
|
+
when Hash
|
51
|
+
directory_node.each do |par, children|
|
52
|
+
define_directory_accessors!(parent, par)
|
53
|
+
define_directory_accessors!(par, children)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
define_method(directory_node) do
|
57
|
+
@paths_cache[directory_node] ||= Pathname.new(instance_variable_get(:"@#{directory_node}"))
|
58
|
+
end
|
59
|
+
define_method("#{directory_node}_abs") do
|
60
|
+
@paths_cache["#{directory_node}_abs"] ||= parent ? send("#{parent}_abs") / Pathname.new(instance_variable_get(:"@#{directory_node}")) : send(directory_node)
|
61
|
+
end
|
45
62
|
end
|
46
63
|
end
|
47
64
|
|
48
|
-
|
49
|
-
abs_method_name = "#{method_name}_abs"
|
50
|
-
define_method(abs_method_name) do
|
51
|
-
@paths_cache[abs_method_name] ||= crystal_src_dir_abs / instance_variable_get(:"@#{method_name}")
|
52
|
-
end
|
65
|
+
define_directory_accessors!(nil, { crystal_project_root: { crystal_src_dir: [:crystal_codegen_dir] } })
|
53
66
|
|
54
|
-
|
55
|
-
|
56
|
-
end
|
67
|
+
def log_level=(level)
|
68
|
+
@logger.level = Logger.const_get(@log_level = level.to_s.upcase)
|
57
69
|
end
|
58
70
|
|
59
|
-
|
60
|
-
abs_method_name = "#{method_name}_abs"
|
61
|
-
define_method(abs_method_name) do
|
62
|
-
@paths_cache[abs_method_name] ||= crystal_project_root / instance_variable_get(:"@#{method_name}")
|
63
|
-
end
|
71
|
+
private
|
64
72
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
def log_level=(level)
|
71
|
-
@log_level = level
|
72
|
-
@logger.level = Logger.const_get(level.to_s.upcase)
|
73
|
+
def read_config
|
74
|
+
YAML.safe_load(IO.read("crystalruby.yaml"))
|
75
|
+
rescue
|
76
|
+
{}
|
73
77
|
end
|
74
78
|
end
|
75
79
|
|