crystalruby 0.1.10 → 0.1.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0499023eb26e35e6db77566876a96357202c0e6cdfafbb49d2aaa67e070c38cd'
4
- data.tar.gz: 4e5f185097b4ad5091b079fc87b43d8d950119c7fbe659a7d76fe473d8cc7cd9
3
+ metadata.gz: 41e8370627d23725b55bb1916254b88690e93b384c94692d59d73b0cf5ceda03
4
+ data.tar.gz: 7d7192ea627331a30f6cd9ff9dcc7482ed2b1e373f115cc0e3cfa19edd5db506
5
5
  SHA512:
6
- metadata.gz: 0d27665a59a9ecc7345bcca13c5e057d71166684350d54bc158bcefe82655025fc6a8c7fd08069f92c17abe849ad465ee995f223b32391e3f49f2ff7f30c8b92
7
- data.tar.gz: ee56f2aead72e533e989b3e80c9550a65fcf78b2677dc070f697d656b1293eefecf0782fa43ac45255fced794a6a54040c32e21571723a1208b3d27babe71851
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"]
data/README.md CHANGED
@@ -501,7 +501,7 @@ Or install it yourself as:
501
501
  $ gem install crystalruby
502
502
  ```
503
503
 
504
- `crystalruby` requires some basic initialization options inside a crystalruby.yaml file in the root of your project.
504
+ `crystalruby` supports some basic configuration options, which can be specified inside a crystalruby.yaml file in the root of your project.
505
505
  You can run `crystalruby init` to generate a configuration file with sane defaults.
506
506
 
507
507
  ```bash
@@ -513,6 +513,21 @@ crystal_src_dir: "./crystalruby/src"
513
513
  crystal_lib_dir: "./crystalruby/lib"
514
514
  crystal_main_file: "main.cr"
515
515
  crystal_lib_name: "crlib"
516
+ crystal_codegen_dir: "generated"
517
+ debug: true
518
+ ```
519
+
520
+ Alternatively, these can be set programmatically:
521
+
522
+ ```ruby
523
+ CrystalRuby.configure do |config|
524
+ config.crystal_src_dir = "./crystalruby/src"
525
+ config.crystal_lib_dir = "./crystalruby/lib"
526
+ config.crystal_main_file = "main.cr"
527
+ config.crystal_lib_name = "crlib"
528
+ config.crystal_codegen_dir = "generated"
529
+ config.debug = true
530
+ end
516
531
  ```
517
532
 
518
533
  ## Development
@@ -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)
data/exe/crystalruby CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "bundler/setup"
4
4
  require "crystalruby"
5
- require 'fileutils'
5
+ require "fileutils"
6
6
 
7
7
  # Define the actions for the commands
8
8
  def init
@@ -14,20 +14,20 @@ def init
14
14
  crystal_main_file: "main.cr"
15
15
  crystal_lib_name: "crlib"
16
16
  crystal_codegen_dir: "generated"
17
+ debug: true
17
18
  YAML
18
19
 
19
20
  # Create the file at the root of the current directory
20
- File.write('crystalruby.yaml', yaml_content)
21
- puts 'Initialized crystalruby.yaml file with dummy content.'
21
+ File.write("crystalruby.yaml", yaml_content)
22
+ puts "Initialized crystalruby.yaml file with dummy content."
22
23
  end
23
24
 
24
-
25
25
  def install
26
26
  Dir.chdir("#{CrystalRuby.config.crystal_src_dir}") do
27
- if system('shards update')
28
- puts 'Shards installed successfully.'
27
+ if system("shards update")
28
+ puts "Shards installed successfully."
29
29
  else
30
- puts 'Error installing shards.'
30
+ puts "Error installing shards."
31
31
  end
32
32
  end
33
33
  clean
@@ -40,7 +40,7 @@ end
40
40
 
41
41
  def build
42
42
  # This is a stub for the build command
43
- puts 'Build command is not implemented yet.'
43
+ puts "Build command is not implemented yet."
44
44
  end
45
45
 
46
46
  # Main program
@@ -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,25 +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, :crystal_lib_name, :crystal_codegen_dir
12
+ attr_accessor :debug, :verbose
13
13
 
14
14
  def initialize
15
15
  @debug = true
16
- config = if File.exist?("crystalruby.yaml")
17
- YAML.safe_load(IO.read("crystalruby.yaml")) rescue {}
18
- else
19
- {}
16
+ @paths_cache = {}
17
+ config = File.exist?("crystalruby.yaml") && begin
18
+ YAML.safe_load(IO.read("crystalruby.yaml"))
19
+ rescue StandardError
20
+ nil
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
+ @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}")
20
57
  end
21
- @crystal_src_dir = config.fetch("crystal_src_dir", "./crystalruby/src")
22
- @crystal_lib_dir = config.fetch("crystal_lib_dir", "./crystalruby/lib")
23
- @crystal_main_file = config.fetch("crystal_main_file", "main.cr")
24
- @crystal_lib_name = config.fetch("crystal_lib_name", "crlib")
25
- @crystal_codegen_dir = config.fetch("crystal_codegen_dir", "generated")
26
58
  end
27
59
  end
28
60
 
29
61
  def self.configure
30
- setup
31
62
  yield(config)
63
+ @paths_cache = {}
32
64
  end
33
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.10"
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.10
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