1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
|
#!/usr/bin/env ruby
#
# Create a stratum to integrate a Ruby project in Baserock, using RubyGems
#
# Copyright (C) 2014 Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
require 'bundler'
require 'optparse'
require 'yaml'
BASEROCK_RUBY_VERSION = '2.0.0'
def parse_options(arguments)
# No options so far ..
opts = OptionParser.new
opts.banner = "Usage: import-ruby PROJECT_DIR OUTPUT_DIR"
opts.separator ""
opts.separator "This tool reads the Gemfile and optionally the " +
"Gemfile.lock from a Ruby project "
opts.separator "source tree in PROJECT_DIR. It outputs a stratum " +
"morphology and a set of chunk "
opts.separator "morphology files to OUTPUT_DIR."
parsed_arguments = opts.parse!(arguments)
if parsed_arguments.length != 2 then
STDERR.puts opts.help
exit 1
end
parsed_arguments
end
def get_project_name(project_dir_name)
# One Git repo can produce any number of Gems, or none, so it's hard to
# work out a project name that way. Instead, use the repo name :)
project_name = File.basename(project_dir_name)
end
def load_gemfile()
# Load and parse the Gemfile and, if found, the Gemfile.lock file.
definition = Bundler::Definition.build(
'Gemfile', 'Gemfile.lock', update=false)
end
def get_all_specs_for_project(dir_name)
Dir.chdir(dir_name) { begin
load_gemfile.specs
rescue Bundler::GemNotFound
# If we're missing some Gem info, try remotely resolving. This is very
# slow so it's nice if it can be avoided. Perhaps setting up a local
# mirror of the necessary specs would avoid this problem. There seems
# to be no way to "reset" the Definition instance after the exception,
# so we have to call load_gemfile again.
STDERR.puts "Resolving definitions remotely (this may take a while!)"
load_gemfile.resolve_remotely!
rescue Bundler::GemfileNotFound
STDERR.puts "Did not find a Gemfile in #{dir_name}."
exit
end }
end
def generate_morphs_for_specset(project_name, specs)
# Chunk names are the Gem's "full name" (name + version number), so that we
# don't break in the rare but possible case that two different versions of
# the same Gem are required for something to work. It'd be nicer to only
# use the full_name if we detect such a conflict. If we do, at least this
# function below can be removed with the much simpler:
# spec.deps.collect |dep| dep.name
runtime_depends = proc do |spec|
result = []
spec.dependencies.each do |dep|
next if dep.type == :development
found = specs[dep]
if found.length != 1
raise Exception,
"Unsure which Gem to use for #{dep}, got #{found}"
end
result << found[0].full_name
end
result
end
description = 'Automatically generated by import-ruby. This is a ' +
'prototype of a method for integrating RubyGems into ' +
'Baserock.'
bin_dir = "\"$DESTDIR/$PREFIX/bin\""
gem_dir = "\"$DESTDIR/$PREFIX/lib/ruby/gems/#{BASEROCK_RUBY_VERSION}\""
chunk_morphs = specs.collect do |spec|
# There's more splitting to be done, but putting the docs in the
# correct artifact is the single biggest win for enabling smaller
# system images.
split_rules = [
{
'artifact' => "#{spec.full_name}-doc",
'include' => [
"usr/lib/ruby/gems/#{BASEROCK_RUBY_VERSION}/doc/.*"
]
}
]
install_commands = [
"mkdir -p #{gem_dir}",
"gem install --install-dir #{gem_dir} --bindir #{bin_dir} " +
"--ignore-dependencies --local #{spec.full_name}.gem"
]
{
'name' => spec.full_name,
'kind' => 'chunk',
'description' => description,
'build-system' => 'manual',
# FIXME: this is not how we should calculate the URL field!
'gem-url' => "http://rubygems.org/downloads/#{spec.full_name}.gem",
'products' => split_rules,
'install-commands' => install_commands
}
end
chunks = specs.collect do |spec|
{
'name' => spec.full_name,
'description' => description,
# This is a dummy value; there is no repo for these chunks.
# The 'repo' field should perhaps become optional!
'repo' => 'baserock:baserock/definitions',
'ref' => 'master',
'morph' => File.join(project_name, spec.full_name + '.morph'),
# Runtime depends must be present at "build" (Gem install) time.
'build-depends' => runtime_depends.call(spec),
# This feature is not in morph.git master yet
'build-mode' => 'rubygem',
}
end
stratum_morph = {
'name' => project_name,
'kind' => 'stratum',
'description' => description,
'build-depends' => [
{ 'morph' => 'ruby' }
],
'chunks' => chunks,
}
return [stratum_morph] + chunk_morphs
end
def write_morphs(morphs, project_name, target_dir_name)
target_dir_name = File.join(target_dir_name, project_name)
FileUtils.makedirs(target_dir_name)
Dir.chdir(target_dir_name) do
morphs.each do |morph|
morph_filename = morph['name'] + '.morph'
File.open(morph_filename, 'w') do |file|
file.write(YAML.dump(morph))
end
end
end
end
def run
project_dir_name, target_dir_name = parse_options(ARGV)
project_name = get_project_name(project_dir_name)
specset = get_all_specs_for_project(project_dir_name)
morphs = generate_morphs_for_specset(project_name, specset)
write_morphs(morphs, project_name, target_dir_name)
end
run
|