crystalruby 0.1.11 → 0.1.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +17 -0
- data/examples/adder/adder.rb +10 -0
- data/lib/crystalruby/compilation.rb +75 -0
- data/lib/crystalruby/config.rb +39 -8
- data/lib/crystalruby/templates/index.cr +1 -0
- data/lib/crystalruby/version.rb +1 -1
- data/lib/crystalruby.rb +48 -60
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41e8370627d23725b55bb1916254b88690e93b384c94692d59d73b0cf5ceda03
|
4
|
+
data.tar.gz: 7d7192ea627331a30f6cd9ff9dcc7482ed2b1e373f115cc0e3cfa19edd5db506
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 56d644c482b04bb8f5b39b6626dda93079578f3f95809294a441e0fd1707aeb71eb253da7a549c351fe448750f6dd2acfdbda3782b37e5aaebb4019332c64d0c
|
7
|
+
data.tar.gz: c529c61b9564f6d23dc551209ac7ce426d56a02424a970b57825f5e993953c376fa57afb3880db0647d581391e7d88881775cd0e2284408997fbaf4e8b37a690
|
data/Dockerfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
FROM ruby:3.3
|
2
|
+
|
3
|
+
MAINTAINER Wouter Coppieters <wc@pico.net.nz>
|
4
|
+
|
5
|
+
RUN apt-get update && apt-get install -y curl gnupg2 software-properties-common lsb-release
|
6
|
+
RUN curl -fsSL https://crystal-lang.org/install.sh | bash
|
7
|
+
WORKDIR /usr/src/app
|
8
|
+
|
9
|
+
COPY Gemfile Gemfile.lock ./
|
10
|
+
COPY crystalruby.gemspec ./
|
11
|
+
COPY lib/crystalruby/version.rb ./lib/crystalruby/version.rb
|
12
|
+
|
13
|
+
RUN bundle install
|
14
|
+
COPY . .
|
15
|
+
|
16
|
+
# Define the command to run your application
|
17
|
+
CMD ["bundle", "exec", "irb"]
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "open3"
|
2
|
+
require "tmpdir"
|
3
|
+
|
4
|
+
module CrystalRuby
|
5
|
+
module Compilation
|
6
|
+
def self.compile!(
|
7
|
+
src: config.crystal_src_dir_abs / config.crystal_main_file,
|
8
|
+
lib: config.crystal_lib_dir_abs / config.crystal_lib_name,
|
9
|
+
verbose: config.verbose,
|
10
|
+
debug: config.debug
|
11
|
+
)
|
12
|
+
Dir.chdir(config.crystal_src_dir_abs) do
|
13
|
+
compile_command = compile_command!(verbose: verbose, debug: debug, lib: lib, src: src)
|
14
|
+
link_command = link_cmd!(verbose: verbose, lib: lib, src: src)
|
15
|
+
|
16
|
+
puts "[crystalruby] Compiling Crystal code: #{compile_command}" if verbose
|
17
|
+
unless system(compile_command)
|
18
|
+
puts "Failed to build Crystal object file."
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
|
22
|
+
puts "[crystalruby] Linking Crystal code: #{link_command}" if verbose
|
23
|
+
unless system(link_command)
|
24
|
+
puts "Failed to link Crystal library."
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.compile_command!(verbose:, debug:, lib:, src:)
|
33
|
+
@compile_command ||= begin
|
34
|
+
verbose_flag = verbose ? "--verbose" : ""
|
35
|
+
debug_flag = debug ? "" : "--release --no-debug"
|
36
|
+
redirect_output = " > /dev/null " unless verbose
|
37
|
+
|
38
|
+
%(crystal build #{verbose_flag} #{debug_flag} --cross-compile -o #{lib} #{src}#{redirect_output})
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Here we misuse the crystal compiler to build a valid linking command
|
43
|
+
# with all of the platform specific flags that we need.
|
44
|
+
# We then use this command to link the object file that we compiled in the previous step.
|
45
|
+
# This is not robust and is likely to need revision in the future.
|
46
|
+
def self.link_cmd!(verbose:, lib:, src:)
|
47
|
+
@link_cmd ||= begin
|
48
|
+
result = nil
|
49
|
+
|
50
|
+
Dir.mktmpdir do |tmp|
|
51
|
+
output, status = Open3.capture2("crystal build --verbose #{src} -o #{Pathname.new(tmp) / "main"}")
|
52
|
+
unless status.success?
|
53
|
+
puts "Failed to compile the Crystal code."
|
54
|
+
exit 1
|
55
|
+
end
|
56
|
+
|
57
|
+
# Parse the output to find the last invocation of the C compiler, which is likely the linking stage
|
58
|
+
# and strip off the targets that the crystal compiler added.
|
59
|
+
link_command_suffix = output.lines.select { |line| line.strip.start_with?("cc") }.last.strip[/.*(-o.*)/, 1]
|
60
|
+
|
61
|
+
# Replace the output file with the path to the object file we compiled
|
62
|
+
link_command_suffix.gsub!(
|
63
|
+
/-o.*main/,
|
64
|
+
"-o #{lib}"
|
65
|
+
)
|
66
|
+
result = %(cc #{lib}.o -shared #{link_command_suffix})
|
67
|
+
result << " > /dev/null 2>&1" unless verbose
|
68
|
+
result
|
69
|
+
end
|
70
|
+
|
71
|
+
result
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/crystalruby/config.rb
CHANGED
@@ -9,26 +9,57 @@ module CrystalRuby
|
|
9
9
|
# Define a nested Config class
|
10
10
|
class Config
|
11
11
|
include Singleton
|
12
|
-
attr_accessor :debug, :
|
13
|
-
:crystal_lib_name, :crystal_codegen_dir
|
12
|
+
attr_accessor :debug, :verbose
|
14
13
|
|
15
14
|
def initialize
|
16
15
|
@debug = true
|
16
|
+
@paths_cache = {}
|
17
17
|
config = File.exist?("crystalruby.yaml") && begin
|
18
18
|
YAML.safe_load(IO.read("crystalruby.yaml"))
|
19
19
|
rescue StandardError
|
20
20
|
nil
|
21
21
|
end || {}
|
22
|
-
@crystal_src_dir
|
23
|
-
@crystal_lib_dir
|
24
|
-
@crystal_main_file
|
25
|
-
@crystal_lib_name
|
26
|
-
@crystal_codegen_dir
|
27
|
-
@
|
22
|
+
@crystal_src_dir = config.fetch("crystal_src_dir", "./crystalruby/src")
|
23
|
+
@crystal_lib_dir = config.fetch("crystal_lib_dir", "./crystalruby/lib")
|
24
|
+
@crystal_main_file = config.fetch("crystal_main_file", "main.cr")
|
25
|
+
@crystal_lib_name = config.fetch("crystal_lib_name", "crlib")
|
26
|
+
@crystal_codegen_dir = config.fetch("crystal_codegen_dir", "generated")
|
27
|
+
@crystal_project_root = config.fetch("crystal_project_root", Pathname.pwd)
|
28
|
+
@debug = config.fetch("debug", true)
|
29
|
+
@verbose = config.fetch("verbose", false)
|
30
|
+
end
|
31
|
+
|
32
|
+
%w[crystal_main_file crystal_lib_name crystal_project_root].each do |method_name|
|
33
|
+
define_method(method_name) do
|
34
|
+
@paths_cache[method_name] ||= Pathname.new(instance_variable_get(:"@#{method_name}"))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
%w[crystal_codegen_dir].each do |method_name|
|
39
|
+
abs_method_name = "#{method_name}_abs"
|
40
|
+
define_method(abs_method_name) do
|
41
|
+
@paths_cache[abs_method_name] ||= crystal_src_dir_abs / instance_variable_get(:"@#{method_name}")
|
42
|
+
end
|
43
|
+
|
44
|
+
define_method(method_name) do
|
45
|
+
@paths_cache[method_name] ||= Pathname.new instance_variable_get(:"@#{method_name}")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
%w[crystal_src_dir crystal_lib_dir].each do |method_name|
|
50
|
+
abs_method_name = "#{method_name}_abs"
|
51
|
+
define_method(abs_method_name) do
|
52
|
+
@paths_cache[abs_method_name] ||= crystal_project_root / instance_variable_get(:"@#{method_name}")
|
53
|
+
end
|
54
|
+
|
55
|
+
define_method(method_name) do
|
56
|
+
@paths_cache[method_name] ||= Pathname.new instance_variable_get(:"@#{method_name}")
|
57
|
+
end
|
28
58
|
end
|
29
59
|
end
|
30
60
|
|
31
61
|
def self.configure
|
32
62
|
yield(config)
|
63
|
+
@paths_cache = {}
|
33
64
|
end
|
34
65
|
end
|
data/lib/crystalruby/version.rb
CHANGED
data/lib/crystalruby.rb
CHANGED
@@ -4,12 +4,15 @@ require "ffi"
|
|
4
4
|
require "digest"
|
5
5
|
require "fileutils"
|
6
6
|
require "method_source"
|
7
|
+
require "pathname"
|
8
|
+
|
7
9
|
require_relative "crystalruby/config"
|
8
10
|
require_relative "crystalruby/version"
|
9
11
|
require_relative "crystalruby/typemaps"
|
10
12
|
require_relative "crystalruby/types"
|
11
13
|
require_relative "crystalruby/typebuilder"
|
12
14
|
require_relative "crystalruby/template"
|
15
|
+
require_relative "crystalruby/compilation"
|
13
16
|
|
14
17
|
module CrystalRuby
|
15
18
|
CR_SRC_FILES_PATTERN = "./**/*.cr"
|
@@ -71,7 +74,7 @@ module CrystalRuby
|
|
71
74
|
function = build_function(self, method_name, args, returns, function_body)
|
72
75
|
CrystalRuby.write_chunk(self, name: function[:name], body: function[:body]) do
|
73
76
|
extend FFI::Library
|
74
|
-
ffi_lib
|
77
|
+
ffi_lib config.crystal_lib_dir / config.crystal_lib_name
|
75
78
|
attach_function method_name, fname, function[:ffi_types], function[:return_ffi_type]
|
76
79
|
if block
|
77
80
|
[singleton_class, self].each do |receiver|
|
@@ -85,7 +88,7 @@ module CrystalRuby
|
|
85
88
|
[singleton_class, self].each do |receiver|
|
86
89
|
receiver.prepend(Module.new do
|
87
90
|
define_method(method_name) do |*args|
|
88
|
-
CrystalRuby.
|
91
|
+
CrystalRuby.build! unless CrystalRuby.compiled?
|
89
92
|
unless CrystalRuby.attached?
|
90
93
|
CrystalRuby.attach!
|
91
94
|
return send(method_name, *args) if block
|
@@ -219,28 +222,29 @@ module CrystalRuby
|
|
219
222
|
raise "Missing config option `#{config_key}`. \nProvide this inside crystalruby.yaml (run `bundle exec crystalruby init` to generate this file with detaults)"
|
220
223
|
end
|
221
224
|
end
|
222
|
-
FileUtils.mkdir_p
|
223
|
-
FileUtils.mkdir_p
|
224
|
-
|
225
|
+
FileUtils.mkdir_p config.crystal_codegen_dir_abs
|
226
|
+
FileUtils.mkdir_p config.crystal_lib_dir_abs
|
227
|
+
FileUtils.mkdir_p config.crystal_src_dir_abs
|
228
|
+
unless File.exist?(config.crystal_src_dir_abs / config.crystal_main_file)
|
225
229
|
IO.write(
|
226
|
-
|
230
|
+
config.crystal_src_dir_abs / config.crystal_main_file,
|
227
231
|
"require \"./#{config.crystal_codegen_dir}/index\"\n"
|
228
232
|
)
|
229
233
|
end
|
230
234
|
|
231
235
|
attach_crystal_ruby_lib! if compiled?
|
232
236
|
|
233
|
-
return if File.exist?(
|
237
|
+
return if File.exist?(config.crystal_src_dir / "shard.yml")
|
234
238
|
|
235
|
-
IO.write("#{config.crystal_src_dir}/shard.yml", <<~
|
239
|
+
IO.write("#{config.crystal_src_dir}/shard.yml", <<~YAML)
|
236
240
|
name: src
|
237
241
|
version: 0.1.0
|
238
|
-
|
242
|
+
YAML
|
239
243
|
end
|
240
244
|
|
241
245
|
def attach_crystal_ruby_lib!
|
242
246
|
extend FFI::Library
|
243
|
-
ffi_lib
|
247
|
+
ffi_lib config.crystal_lib_dir / config.crystal_lib_name
|
244
248
|
attach_function "init!", :init, [:pointer], :void
|
245
249
|
send(:remove_const, :ErrorCallback) if defined?(ErrorCallback)
|
246
250
|
const_set(:ErrorCallback, FFI::Function.new(:void, %i[string string]) do |error_type, message|
|
@@ -272,24 +276,19 @@ module CrystalRuby
|
|
272
276
|
|
273
277
|
def type_modules
|
274
278
|
(@types_cache || {}).map do |type_name, expr|
|
275
|
-
typedef = ""
|
276
279
|
parts = type_name.split("::")
|
277
|
-
|
278
|
-
|
279
|
-
typedef << "#{indent} module #{part}\n"
|
280
|
-
indent += " "
|
280
|
+
typedef = parts[0...-1].each_with_index.reduce("") do |acc, (part, index)|
|
281
|
+
acc + "#{" " * index}module #{part}\n"
|
281
282
|
end
|
282
|
-
typedef
|
283
|
-
parts[0...-1].
|
284
|
-
|
285
|
-
typedef << "#{indent} end\n"
|
283
|
+
typedef += "#{" " * (parts.size - 1)}alias #{parts.last} = #{expr}\n"
|
284
|
+
typedef + parts[0...-1].reverse.each_with_index.reduce("") do |acc, (_part, index)|
|
285
|
+
acc + "#{" " * (parts.size - 2 - index)}end\n"
|
286
286
|
end
|
287
|
-
typedef
|
288
287
|
end.join("\n")
|
289
288
|
end
|
290
289
|
|
291
290
|
def self.requires
|
292
|
-
|
291
|
+
chunk_store.map do |function|
|
293
292
|
function_data = function[:body]
|
294
293
|
file_digest = Digest::MD5.hexdigest function_data
|
295
294
|
fname = function[:name]
|
@@ -297,40 +296,26 @@ module CrystalRuby
|
|
297
296
|
end.join("\n")
|
298
297
|
end
|
299
298
|
|
300
|
-
def self.
|
301
|
-
|
302
|
-
|
303
|
-
index_content = Template.render(
|
299
|
+
def self.build!
|
300
|
+
File.write config.crystal_codegen_dir_abs / "index.cr", Template.render(
|
304
301
|
Template::Index,
|
305
|
-
|
306
|
-
|
307
|
-
requires: requires
|
308
|
-
}
|
302
|
+
type_modules: type_modules,
|
303
|
+
requires: requires
|
309
304
|
)
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
end
|
320
|
-
|
321
|
-
unless result = system(cmd)
|
322
|
-
File.delete(digest_file_name) if File.exist?(digest_file_name)
|
323
|
-
raise "Error compiling crystal code"
|
324
|
-
end
|
305
|
+
if @compiled = CrystalRuby::Compilation.compile!(
|
306
|
+
verbose: config.verbose,
|
307
|
+
debug: config.debug
|
308
|
+
)
|
309
|
+
IO.write(digest_file_name, get_cr_src_files_digest)
|
310
|
+
attach_crystal_ruby_lib!
|
311
|
+
else
|
312
|
+
File.delete(digest_file_name) if File.exist?(digest_file_name)
|
313
|
+
raise "Error compiling crystal code"
|
325
314
|
end
|
326
|
-
|
327
|
-
IO.write(digest_file_name, get_cr_src_files_digest)
|
328
|
-
@compiled = true
|
329
|
-
attach_crystal_ruby_lib!
|
330
315
|
end
|
331
316
|
|
332
317
|
def self.attach!
|
333
|
-
@
|
318
|
+
@chunk_store.each do |function|
|
334
319
|
function[:compile_callback]&.call
|
335
320
|
end
|
336
321
|
@attached = true
|
@@ -345,7 +330,11 @@ module CrystalRuby
|
|
345
330
|
end
|
346
331
|
|
347
332
|
def self.digest_file_name
|
348
|
-
@digest_file_name ||= "#{config.
|
333
|
+
@digest_file_name ||= config.crystal_lib_dir_abs / "#{config.crystal_lib_name}.digest"
|
334
|
+
end
|
335
|
+
|
336
|
+
def self.chunk_store
|
337
|
+
@chunk_store ||= []
|
349
338
|
end
|
350
339
|
|
351
340
|
def self.get_current_crystal_lib_digest
|
@@ -353,26 +342,25 @@ module CrystalRuby
|
|
353
342
|
end
|
354
343
|
|
355
344
|
def self.write_chunk(owner, body:, name: Digest::MD5.hexdigest(body), &compile_callback)
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
@block_store.each do |function|
|
345
|
+
chunk_store << { owner: owner, name: name, body: body, compile_callback: compile_callback }
|
346
|
+
FileUtils.mkdir_p(config.crystal_codegen_dir_abs)
|
347
|
+
existing = Dir.glob("#{config.crystal_codegen_dir_abs}/**/*.cr")
|
348
|
+
chunk_store.each do |function|
|
361
349
|
owner_name = function[:owner].name
|
362
|
-
FileUtils.mkdir_p(
|
350
|
+
FileUtils.mkdir_p(config.crystal_codegen_dir_abs / owner_name)
|
363
351
|
function_data = function[:body]
|
364
352
|
fname = function[:name]
|
365
353
|
file_digest = Digest::MD5.hexdigest function_data
|
366
|
-
filename =
|
367
|
-
unless existing.delete(filename)
|
354
|
+
filename = config.crystal_codegen_dir_abs / owner_name / "#{fname}_#{file_digest}.cr"
|
355
|
+
unless existing.delete(filename.to_s)
|
368
356
|
@compiled = false
|
369
357
|
@attached = false
|
370
358
|
File.write(filename, function_data)
|
371
359
|
end
|
372
360
|
existing.select do |f|
|
373
|
-
f =~
|
361
|
+
f =~ /#{config.crystal_codegen_dir / owner_name / "#{fname}_[a-f0-9]{32}\.cr"}/
|
374
362
|
end.each do |fl|
|
375
|
-
File.delete(fl) unless fl.eql?(filename)
|
363
|
+
File.delete(fl) unless fl.eql?(filename.to_s)
|
376
364
|
end
|
377
365
|
end
|
378
366
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: crystalruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wouter Coppieters
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-04-
|
11
|
+
date: 2024-04-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: digest
|
@@ -77,12 +77,15 @@ files:
|
|
77
77
|
- ".rubocop.yml"
|
78
78
|
- CHANGELOG.md
|
79
79
|
- CODE_OF_CONDUCT.md
|
80
|
+
- Dockerfile
|
80
81
|
- LICENSE.txt
|
81
82
|
- README.md
|
82
83
|
- Rakefile
|
83
84
|
- crystalruby.gemspec
|
85
|
+
- examples/adder/adder.rb
|
84
86
|
- exe/crystalruby
|
85
87
|
- lib/crystalruby.rb
|
88
|
+
- lib/crystalruby/compilation.rb
|
86
89
|
- lib/crystalruby/config.rb
|
87
90
|
- lib/crystalruby/template.rb
|
88
91
|
- lib/crystalruby/templates/function.cr
|