thermite-rails 0.2.0
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 +7 -0
- data/.gitignore +34 -0
- data/.rspec +3 -0
- data/.rubocop.yml +43 -0
- data/CHANGELOG.md +14 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +10 -0
- data/MIT-LICENSE +20 -0
- data/README.md +131 -0
- data/Rakefile +15 -0
- data/bin/console +15 -0
- data/bin/rails +16 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/bin/rubocop +29 -0
- data/bin/setup +8 -0
- data/lib/thermite/rails.rb +31 -0
- data/lib/thermite/rails/cargo_runner.rb +20 -0
- data/lib/thermite/rails/check_outdated.rb +19 -0
- data/lib/thermite/rails/generators/crate_generator.rb +100 -0
- data/lib/thermite/rails/generators/install_generator.rb +14 -0
- data/lib/thermite/rails/project.rb +88 -0
- data/lib/thermite/rails/railtie.rb +37 -0
- data/lib/thermite/rails/root_project.rb +46 -0
- data/lib/thermite/rails/tasks/project_rake_task.rb +81 -0
- data/lib/thermite/rails/tasks/project_spec_task.rb +33 -0
- data/lib/thermite/rails/tasks/project_thermite_build_task.rb +21 -0
- data/lib/thermite/rails/tasks/project_thermite_clean_task.rb +21 -0
- data/lib/thermite/rails/tasks/project_thermite_test_task.rb +21 -0
- data/lib/thermite/rails/tasks/project_thermite_update_task.rb +28 -0
- data/lib/thermite/rails/tasks/root_project_rake_task.rb +74 -0
- data/lib/thermite/rails/tasks/root_project_spec_task.rb +40 -0
- data/lib/thermite/rails/tasks/root_project_thermite_build_task.rb +27 -0
- data/lib/thermite/rails/tasks/root_project_thermite_clean_task.rb +27 -0
- data/lib/thermite/rails/tasks/root_project_thermite_test_task.rb +27 -0
- data/lib/thermite/rails/tasks/root_project_thermite_update_task.rb +27 -0
- data/lib/thermite/rails/tasks/rspec.rake +14 -0
- data/lib/thermite/rails/tasks/thermite.rake +18 -0
- data/lib/thermite/rails/version.rb +7 -0
- data/thermite-rails.gemspec +47 -0
- metadata +237 -0
data/bin/rubocop
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rubocop' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require 'pathname'
|
12
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path('bundle', __dir__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 150).match?(/This file was generated by Bundler/)
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'bundler/setup'
|
28
|
+
|
29
|
+
load Gem.bin_path('rubocop', 'rubocop')
|
data/bin/setup
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'rails/version'
|
4
|
+
require_relative 'rails/railtie'
|
5
|
+
|
6
|
+
module Thermite
|
7
|
+
module Rails
|
8
|
+
# @param root_path [String]
|
9
|
+
# @return [String] Path to the root of the (Rails) project.
|
10
|
+
def self.find_root(root_path)
|
11
|
+
root = File.expand_path(root_path)
|
12
|
+
|
13
|
+
dir = root
|
14
|
+
|
15
|
+
loop do
|
16
|
+
return dir if yield(dir)
|
17
|
+
|
18
|
+
new_dir = File.dirname(dir)
|
19
|
+
raise "Unable to find root for #{root}" if new_dir == dir
|
20
|
+
|
21
|
+
dir = new_dir
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Thermite::Rails::RootProject]
|
26
|
+
def self.root_project
|
27
|
+
require_relative 'rails/root_project'
|
28
|
+
@root_project ||= Thermite::Rails::RootProject.new(::Rails.root.to_s)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thermite/cargo'
|
4
|
+
|
5
|
+
module Thermite
|
6
|
+
module Rails
|
7
|
+
# Just a wrapper object for thermite's {{Thermite::Cargo}} module.
|
8
|
+
class CargoRunner
|
9
|
+
include Thermite::Cargo
|
10
|
+
include FileUtils
|
11
|
+
|
12
|
+
attr_reader :config
|
13
|
+
|
14
|
+
# @param config [Thermite::Config]
|
15
|
+
def initialize(config)
|
16
|
+
@config = config
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Thermite
|
4
|
+
module Rails
|
5
|
+
# Rack middleware for ensuring projects have been built.
|
6
|
+
class CheckOutdated
|
7
|
+
def initialize(app, project)
|
8
|
+
@app = app
|
9
|
+
@project = project
|
10
|
+
@last_check = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
@project.ensure_built!
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thermite/cargo'
|
4
|
+
require 'thermite/config'
|
5
|
+
require_relative '../project'
|
6
|
+
|
7
|
+
module Thermite
|
8
|
+
module Rails
|
9
|
+
class CrateGenerator < ::Rails::Generators::Base
|
10
|
+
class CargoRunner
|
11
|
+
include Singleton
|
12
|
+
include Thermite::Cargo
|
13
|
+
end
|
14
|
+
|
15
|
+
namespace 'thermite:crate'
|
16
|
+
desc 'add new thermite crate'
|
17
|
+
argument :name, type: :string
|
18
|
+
|
19
|
+
def start
|
20
|
+
say "Creating crate '#{name}'..."
|
21
|
+
end
|
22
|
+
|
23
|
+
def crates_full_path
|
24
|
+
@crates_full_path ||= ::Rails.root.join('crates').to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def project_full_path
|
28
|
+
@project_full_path ||= File.join(crates_full_path, name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def project
|
32
|
+
@project ||= Thermite::Rails::Project.new(project_full_path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def ext_full_path
|
36
|
+
@ext_full_path ||= File.join(project_full_path, 'ext')
|
37
|
+
end
|
38
|
+
|
39
|
+
def make_crates_dir
|
40
|
+
empty_directory(crates_full_path)
|
41
|
+
end
|
42
|
+
|
43
|
+
def run_crate_new_and_bundle_gem
|
44
|
+
inside(crates_full_path, verbose: true) do
|
45
|
+
run "#{CargoRunner.instance.cargo} new #{name}"
|
46
|
+
run "bundle gem #{name}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def update_cargo_toml
|
51
|
+
gsub_file(project.cargo_toml_path, "\n[dependencies]") do
|
52
|
+
%(publish = false\n\n[lib]\ncrate-type = ["cdylib"]\n\n[dependencies])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_thermite_to_gemspec
|
57
|
+
insert_into_file(project.gemspec_path, after: %( spec.require_paths = ["lib"])) do
|
58
|
+
lines = %(\n spec.extensions << 'ext/Rakefile'\n\n)
|
59
|
+
lines + %( spec.add_runtime_dependency 'thermite', '~> 0')
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_ext_directory
|
64
|
+
empty_directory(ext_full_path)
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_crate_ext_rakefile
|
68
|
+
rakefile_path = File.join(ext_full_path, 'Rakefile')
|
69
|
+
|
70
|
+
create_file(rakefile_path) do
|
71
|
+
<<~THERM
|
72
|
+
require 'thermite/tasks'
|
73
|
+
|
74
|
+
project_dir = File.dirname(File.dirname(__FILE__))
|
75
|
+
Thermite::Tasks.new(cargo_project_path: project_dir, ruby_project_path: project_dir)
|
76
|
+
task default: %w(thermite:build)
|
77
|
+
THERM
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def update_crate_rakefile
|
82
|
+
append_to_file(project.rakefile_path) do
|
83
|
+
%(\nrequire 'thermite/tasks'\n\nThermite::Tasks.new)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def fix_rubocops
|
88
|
+
run "bundle exec rubocop -a #{project_full_path}" if File.read('Gemfile').include?('rubocop')
|
89
|
+
end
|
90
|
+
|
91
|
+
def update_rails_gemfile
|
92
|
+
gem name, path: "crates/#{name}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def bundle_install
|
96
|
+
run 'bundle'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Thermite
|
4
|
+
module Rails
|
5
|
+
class InstallGenerator < ::Rails::Generators::Base
|
6
|
+
namespace 'thermite:install'
|
7
|
+
desc 'install thermite'
|
8
|
+
|
9
|
+
def configure_environment
|
10
|
+
environment 'config.thermite.outdated_error = :page_load', env: :development
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tomlrb'
|
4
|
+
require 'thermite/config'
|
5
|
+
|
6
|
+
module Thermite
|
7
|
+
module Rails
|
8
|
+
# A "project" is a crate that contains both Rust and Ruby code. Usually one
|
9
|
+
# of these will live at [rails root]/crates/[project].
|
10
|
+
class Project
|
11
|
+
class OutdatedBuildError < StandardError
|
12
|
+
# @param name [String] The crate name.
|
13
|
+
def initialize(name)
|
14
|
+
msg = "\n\nThermite crate '#{name}' is outdated. To resolve this issue, " \
|
15
|
+
"run `rake thermite:build:#{name}` and restart your server.\n\n"
|
16
|
+
|
17
|
+
super(msg)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :project_path, :config
|
22
|
+
|
23
|
+
# @param project_path [String]
|
24
|
+
def initialize(project_path)
|
25
|
+
@project_path = find_project(project_path)
|
26
|
+
@config = Thermite::Config.new(ruby_project_path: @project_path, cargo_project_path: @project_path)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [String] Path to the project's Cargo.toml file.
|
30
|
+
def cargo_toml_path
|
31
|
+
File.join(@project_path, 'Cargo.toml')
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [String] Path to the project's Cargo.toml file.
|
35
|
+
def gemspec_path
|
36
|
+
File.join(@project_path, "#{crate_name}.gemspec")
|
37
|
+
end
|
38
|
+
|
39
|
+
def rakefile_path
|
40
|
+
File.join(@project_path, 'Rakefile')
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [String] Path to `[project root]/spec/`.
|
44
|
+
def spec_path
|
45
|
+
File.join(@project_path, 'spec')
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [String] "package.name" from the crate's Cargo.toml file.
|
49
|
+
def crate_name
|
50
|
+
@crate_name ||= Tomlrb.load_file(cargo_toml_path)['package']['name']
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Boolean] Does this project have `[project root]/spec/`?
|
54
|
+
def specs?
|
55
|
+
File.exist?(spec_path)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Boolean] Does this project use thermite?
|
59
|
+
def thermite?
|
60
|
+
return false unless File.exist?(gemspec_path)
|
61
|
+
|
62
|
+
File.read(gemspec_path).include? 'thermite'
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Boolean] Has the crate been updated since it was last built?
|
66
|
+
def outdated_build?
|
67
|
+
mtime = Dir["#{@project_path}/src/**/*.rs"].map { |file| File.mtime(file) }.max
|
68
|
+
native = "#{@project_path}/lib/#{@config.shared_library}"
|
69
|
+
|
70
|
+
!File.exist?(native) || File.mtime(native) < mtime
|
71
|
+
end
|
72
|
+
|
73
|
+
# Raise if the project is outdated.
|
74
|
+
def ensure_built!
|
75
|
+
raise OutdatedBuildError, crate_name if outdated_build?
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# @param project_path [String]
|
81
|
+
def find_project(project_path)
|
82
|
+
Thermite::Rails.find_root(project_path) do |dir|
|
83
|
+
File.exist?("#{dir}/Cargo.toml")
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/railtie'
|
4
|
+
require_relative 'check_outdated'
|
5
|
+
require_relative 'root_project'
|
6
|
+
|
7
|
+
module Thermite
|
8
|
+
module Rails
|
9
|
+
class Railtie < ::Rails::Railtie
|
10
|
+
config.thermite = ActiveSupport::OrderedOptions.new
|
11
|
+
|
12
|
+
rake_tasks do
|
13
|
+
load File.expand_path(File.join('tasks', 'thermite.rake'), __dir__)
|
14
|
+
load File.expand_path(File.join('tasks', 'rspec.rake'), __dir__) if defined?(::RSpec)
|
15
|
+
end
|
16
|
+
|
17
|
+
generators do
|
18
|
+
require_relative 'generators/crate_generator'
|
19
|
+
require_relative 'generators/install_generator'
|
20
|
+
end
|
21
|
+
|
22
|
+
initializer 'thermite.build_check' do |app|
|
23
|
+
project = Thermite::Rails::RootProject.new(app.root)
|
24
|
+
|
25
|
+
# This will be added by the install generator, but people may forget to run it
|
26
|
+
if ::Rails.env.development? && !config.thermite.key?(:outdated_error)
|
27
|
+
config.thermite.outdated_error = :page_load
|
28
|
+
end
|
29
|
+
|
30
|
+
if config.thermite.delete(:outdated_error) == :page_load
|
31
|
+
config.app_middleware.insert_after ::ActionDispatch::Callbacks,
|
32
|
+
Thermite::Rails::CheckOutdated, project
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'project'
|
4
|
+
|
5
|
+
module Thermite
|
6
|
+
module Rails
|
7
|
+
# A root project is really just the Rails project, which can have a number of
|
8
|
+
# nested "projects" under crates/[project name].
|
9
|
+
class RootProject
|
10
|
+
attr_reader :path
|
11
|
+
|
12
|
+
# @param path [String]
|
13
|
+
def initialize(path)
|
14
|
+
@path = find_root(path)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Array<Thermite::Rails::Project>]
|
18
|
+
def projects
|
19
|
+
@projects ||= Dir["#{@path}/crates/*"].
|
20
|
+
find_all { |f| File.exist?("#{f}/Cargo.toml") }.
|
21
|
+
find_all { |f| File.exist?("#{f}/Cargo.toml") }.
|
22
|
+
map { |d| Thermite::Rails::Project.new(d) }.
|
23
|
+
find_all(&:thermite?)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Array<Thermite::Rails::Project>]
|
27
|
+
def projects_with_specs
|
28
|
+
projects.find_all(&:specs?)
|
29
|
+
end
|
30
|
+
|
31
|
+
def ensure_built!
|
32
|
+
projects.each(&:ensure_built!)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# @param path [String]
|
38
|
+
# @return [String]
|
39
|
+
def find_root(path)
|
40
|
+
Thermite::Rails.find_root(path) do |dir|
|
41
|
+
!Dir["#{dir}/{Gemfile,*.gemspec}"].empty?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake/tasklib'
|
4
|
+
require 'thor/shell/color'
|
5
|
+
|
6
|
+
module Thermite
|
7
|
+
module Rails
|
8
|
+
module Tasks
|
9
|
+
# Base class to be used for creating Rake tasks for individual Projects.
|
10
|
+
class ProjectRakeTask < ::Rake::TaskLib
|
11
|
+
delegate :crate_name, :project_path, to: :@project
|
12
|
+
|
13
|
+
# @param project [Thermite::Rails::Project]
|
14
|
+
def initialize(project)
|
15
|
+
@project = project
|
16
|
+
@shell = Thor::Shell::Color.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [String]
|
20
|
+
def crate_name_for_ruby
|
21
|
+
crate_name.underscore
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param [String]
|
25
|
+
def task_name
|
26
|
+
raise 'Define in child'
|
27
|
+
end
|
28
|
+
|
29
|
+
# In your child class, this method should `define` the Rake task.
|
30
|
+
def define_rake_task
|
31
|
+
return if Rake::Task.task_defined?(task_name)
|
32
|
+
|
33
|
+
raise 'Define in child'
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Boolean] Has a task with this name already been defined?
|
37
|
+
def defined?
|
38
|
+
Rake::Task.task_defined?(task_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param message [String]
|
42
|
+
# @param color Whatever is supported by highline.
|
43
|
+
def color_puts(message, color = :blue)
|
44
|
+
@shell.say "[#{crate_name}] #{message}", color
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @param task_name [String]
|
50
|
+
def run_task(task_name)
|
51
|
+
color_puts("Running task: #{task_name}", :blue)
|
52
|
+
|
53
|
+
load_and do
|
54
|
+
Rake::Task[task_name].invoke
|
55
|
+
end
|
56
|
+
rescue RuntimeError => ex
|
57
|
+
if ex.message.match?("Don't know how to build task '#{task_name}'")
|
58
|
+
color_puts("#{ex.message}; skipping", :yellow)
|
59
|
+
else
|
60
|
+
abort ex.message
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_and
|
65
|
+
Dir.chdir(project_path) do
|
66
|
+
load_rakefile do
|
67
|
+
yield
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def load_rakefile
|
73
|
+
load 'Rakefile'
|
74
|
+
yield
|
75
|
+
rescue LoadError => ex
|
76
|
+
color_puts("Skipping due to LoadError: #{ex.message}", :red)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|