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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3cf95b936e86d15e4cbfc45d38ee8ec4bf81a2db762fde8bcfc848d5c73dc3d6
4
- data.tar.gz: 05d21217238b8a82b809005de6bed2af202b1d1f4fbd5f4da67588cd83f45428
3
+ metadata.gz: 41e8370627d23725b55bb1916254b88690e93b384c94692d59d73b0cf5ceda03
4
+ data.tar.gz: 7d7192ea627331a30f6cd9ff9dcc7482ed2b1e373f115cc0e3cfa19edd5db506
5
5
  SHA512:
6
- metadata.gz: 88306803e6fe56de62ceb826128767879f0cf46d1656e852deb3e98519d8dd304f151ca03ba8c505915ecc164c8663a19bdb5ea28d751e47e32cc4e8fc555902
7
- data.tar.gz: 6b0621a641bf771a9313b652e1d503bfb734942ee1135188ad7baf68d08850577b581cb76a941861c5e2270744e289c32ce720ea4500729483ca50bd365d927f
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,10 @@
1
+ require "crystalruby"
2
+
3
+ module Adder
4
+ crystalize [a: :int, b: :int] => :int
5
+ def add(a, b)
6
+ a + b
7
+ end
8
+ end
9
+
10
+ puts Adder.add(1, 2)
@@ -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
@@ -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, :crystal_src_dir, :crystal_lib_dir, :crystal_main_file,
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 = 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
- @debug = config.fetch("debug", "true")
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
@@ -43,4 +43,5 @@ end
43
43
  %{type_modules}
44
44
 
45
45
  # Require all generated crystal files
46
+ require "json"
46
47
  %{requires}
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Crystalruby
4
- VERSION = "0.1.11"
4
+ VERSION = "0.1.12"
5
5
  end
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 "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
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.compile! unless CrystalRuby.compiled?
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 "#{config.crystal_src_dir}/#{config.crystal_codegen_dir}"
223
- FileUtils.mkdir_p "#{config.crystal_lib_dir}"
224
- unless File.exist?("#{config.crystal_src_dir}/#{config.crystal_main_file}")
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
- "#{config.crystal_src_dir}/#{config.crystal_main_file}",
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?("#{config.crystal_src_dir}/shard.yml")
237
+ return if File.exist?(config.crystal_src_dir / "shard.yml")
234
238
 
235
- IO.write("#{config.crystal_src_dir}/shard.yml", <<~CRYSTAL)
239
+ IO.write("#{config.crystal_src_dir}/shard.yml", <<~YAML)
236
240
  name: src
237
241
  version: 0.1.0
238
- CRYSTAL
242
+ YAML
239
243
  end
240
244
 
241
245
  def attach_crystal_ruby_lib!
242
246
  extend FFI::Library
243
- ffi_lib "#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
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
- indent = ""
278
- parts[0...-1].each do |part|
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 << "#{indent}alias #{parts[-1]} = #{expr}\n"
283
- parts[0...-1].each do |_part|
284
- indent = indent[0...-2]
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
- @block_store.map do |function|
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.compile!
301
- return unless @block_store
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
- type_modules: type_modules,
307
- requires: requires
308
- }
302
+ type_modules: type_modules,
303
+ requires: requires
309
304
  )
310
-
311
- File.write("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/index.cr", index_content)
312
- lib_target = "#{Dir.pwd}/#{config.crystal_lib_dir}/#{config.crystal_lib_name}"
313
-
314
- Dir.chdir(config.crystal_src_dir) do
315
- cmd = if config.debug
316
- "crystal build -o #{lib_target} #{config.crystal_main_file}"
317
- else
318
- "crystal build --release --no-debug -o #{lib_target} #{config.crystal_main_file}"
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
- @block_store.each do |function|
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.crystal_lib_dir}/#{config.crystal_lib_name}.digest"
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
- @block_store ||= []
357
- @block_store << { owner: owner, name: name, body: body, compile_callback: compile_callback }
358
- FileUtils.mkdir_p("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}")
359
- existing = Dir.glob("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/**/*.cr")
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("#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/#{owner_name}")
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 = "#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/#{owner_name}/#{fname}_#{file_digest}.cr"
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 =~ %r{#{config.crystal_src_dir}/#{config.crystal_codegen_dir}/#{owner_name}/#{fname}_[a-f0-9]{32}\.cr}
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.11
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 00:00:00.000000000 Z
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