crate 0.1.1

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.
@@ -0,0 +1,36 @@
1
+ require 'digest/md5'
2
+ require 'digest/sha1'
3
+
4
+ module Crate
5
+ #
6
+ # A wrapper around a given digest value and the algorithm that creates it.
7
+ # Use it to verify a bytestream.
8
+ #
9
+ class Digest
10
+
11
+ attr_reader :engine
12
+ attr_reader :hex
13
+
14
+ class << self
15
+ def sha1( hex )
16
+ return Crate::Digest.new( hex, ::Digest::SHA1 )
17
+ end
18
+
19
+ def md5( hex )
20
+ return Crate::Digest.new( hex, ::Digest::MD5 )
21
+ end
22
+ end
23
+
24
+ def initialize( hex, klass )
25
+ @hex = hex
26
+ @engine = klass.new
27
+ end
28
+
29
+ #
30
+ # verify that the given filename has a digest value
31
+ #
32
+ def valid?( filename )
33
+ engine.hexdigest( IO.read( filename ) ) == hex
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,91 @@
1
+ require 'crate/dependency'
2
+ require 'rubygems/format'
3
+ module Crate
4
+ class GemIntegration < Dependency
5
+ #
6
+ # Define all the tasks in the namespace of the +name+ of this task.
7
+ #
8
+ # The dependency chain is:
9
+ #
10
+ # :integrate => :patch => :unpack => :verify => :download
11
+ #
12
+ #
13
+ def define
14
+ logger.debug "Defining tasks for #{name} #{version}"
15
+
16
+ namespace name do
17
+ define_download
18
+ define_verify
19
+ define_unpack
20
+ define_patch
21
+ define_integration
22
+
23
+ task :done => "#{name}:integrate"
24
+ task :default => "#{name}:done"
25
+ end
26
+
27
+ desc "Build and Integrate #{name} #{version}"
28
+ task name => "#{name}:default"
29
+ end
30
+
31
+ #
32
+ # Define how the gem integrates into the ruby installation
33
+ #
34
+ def define_integration
35
+ desc "Integrate #{name} into ruby's build tree"
36
+ task :integration => [ "#{name}:patch", "ruby:patch" ] do |t|
37
+ logger.info "Integrating #{name} into ruby's tree"
38
+ format = Gem::Format.from_file_by_path( local_source )
39
+
40
+ require_paths = format.spec.require_paths.dup
41
+ integration_info = {}
42
+
43
+ format.spec.extensions.each do |ext|
44
+ logger.info "integrating #{name} extension #{ext}"
45
+ ext_dirname = File.dirname( ext ) + File::SEPARATOR
46
+ dest_ext_dir = File.join( Crate.ruby.ext_dir, name )
47
+ integration_info[ ext_dirname ] = dest_ext_dir
48
+ require_paths.delete( File.dirname( ext ) )
49
+ end
50
+
51
+ require_paths.each do |rp|
52
+ logger.info "integrating #{name} '#{rp}' files "
53
+ integration_info[ rp + File::SEPARATOR ] = Crate.ruby.lib_dir
54
+ end
55
+
56
+ install_integration_files( integration_info )
57
+
58
+ setup_lines = IO.readlines( Crate.ruby.ext_setup_file )
59
+ if setup_lines.grep(/^#{name}/).empty? then
60
+ File.open( Crate.ruby.ext_setup_file, "a+" ) do |f|
61
+ logger.info "updating ext/Setup file to add #{name}"
62
+ f.puts name
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ #
69
+ # each of the key value pairs indicates a matching path (the key) from the
70
+ # gemspec that should be installed into the designated destiation path (the
71
+ # value)
72
+ #
73
+ def install_integration_files( info )
74
+ format = Gem::Format.from_file_by_path( local_source )
75
+ info.each_pair do |from, to|
76
+ Dir.chdir( File.join( pkg_dir, from ) ) do
77
+ format.spec.files.each do |f|
78
+ if f.index( from ) == 0 then
79
+ src_file = f.sub( from, '' )
80
+ dest_file = File.join( to, src_file )
81
+ dest_dir = File.dirname( dest_file )
82
+ logger.debug "copy #{src_file} to #{dest_file}"
83
+ FileUtils.mkdir_p dest_dir unless File.directory?( dest_dir )
84
+ FileUtils.cp src_file, dest_file
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,173 @@
1
+ require 'crate'
2
+
3
+ require 'rubygems'
4
+ require 'optparse'
5
+ require 'find'
6
+ require 'fileutils'
7
+
8
+ module Crate
9
+ # The Crate::Main class contains all the functionality needed by the +crate+
10
+ # command line application. Much of this code is derived from the Webby::Main
11
+ # class
12
+ class Main
13
+
14
+ # Directory where the Crate project will be created
15
+ attr_accessor :project
16
+
17
+ # Directory where the template Crate project is located
18
+ attr_accessor :data
19
+
20
+ # behavior options
21
+ attr_accessor :options
22
+
23
+ #
24
+ # Create a new instance of Crate and run with the command line _args_.
25
+ #
26
+ def self.run( args )
27
+ m = self.new
28
+ m.parse( args )
29
+ m.create_project
30
+ end
31
+
32
+ #
33
+ # default options
34
+ #
35
+ def default_options
36
+ o = OpenStruct.new
37
+ o.force = false
38
+ return o
39
+ end
40
+
41
+ #
42
+ # Create a new Crate object
43
+ #
44
+ def initialize
45
+ @log = Logging::Logger[self]
46
+ @options = self.default_options
47
+ end
48
+
49
+ #
50
+ # The option parser for Crate
51
+ #
52
+ def option_parser
53
+ OptionParser.new do |op|
54
+ op.banner << " project"
55
+
56
+ op.on("-f", "--force", "force the overwriting of existing files") do
57
+ self.options.force = true
58
+ end
59
+ op.separator ""
60
+ op.separator "common options:"
61
+ op.on_tail( "-h", "--help", "show this message") do
62
+ puts op
63
+ exit 0
64
+ end
65
+
66
+ op.on_tail( "--version", "show version" ) do
67
+ puts "Crate #{::Crate::VERSION}"
68
+ exit 0
69
+ end
70
+ end
71
+ end
72
+
73
+ #
74
+ # Parse the command line arguments
75
+ #
76
+ def parse( argv )
77
+ self.data = ::Crate.data_path
78
+ opts = option_parser
79
+ begin
80
+ opts.parse!( argv )
81
+ self.project = argv.shift
82
+
83
+ if project.nil?
84
+ puts opts
85
+ exit 1
86
+ end
87
+ rescue ::OptionParser::ParseError => pe
88
+ puts "#{opts.program_name}: #{pe}"
89
+ puts "Try `#{opts.program_name} --help` for more information"
90
+ exit 1
91
+ end
92
+ end
93
+
94
+ #
95
+ # Create a new Crate project
96
+ #
97
+ def create_project
98
+ unless options.force
99
+ abort "'#{project}' already exists" if File.exist?( project )
100
+ end
101
+
102
+ # copy over files from the master project data diretory in crate
103
+ files = project_files
104
+ files.keys.sort.each do |dir|
105
+ mkdir dir
106
+ files[dir].sort.each do |file|
107
+ cp file
108
+ end
109
+ end
110
+ end
111
+
112
+ #
113
+ # Make a directory in the specified directory under the project directory
114
+ # and display a message on the screen indicating that the directory is being
115
+ # created.
116
+ #
117
+ def mkdir( dir )
118
+ dir = dir.empty? ? project : ::File.join( project, dir )
119
+ unless File.directory?( dir )
120
+ creating dir
121
+ FileUtils.mkdir_p dir
122
+ end
123
+ end
124
+
125
+ #
126
+ # Copy a file from the Crate prototype location to the project location.
127
+ # Display a message that the file is being created.
128
+ #
129
+ def cp( file )
130
+ src = ::File.join( data, file )
131
+ dest = ::File.join( project, file )
132
+ creating dest
133
+ FileUtils.cp( src, dest )
134
+ end
135
+
136
+ #
137
+ # log a creating message
138
+ #
139
+ def creating( msg )
140
+ @log.info "creating #{msg}"
141
+ end
142
+
143
+ #
144
+ # log a fatal message and abort
145
+ #
146
+ def abort( msg )
147
+ @log.fatal msg
148
+ exit 1
149
+ end
150
+
151
+ #
152
+ # Iterate over all the files in the Crate project template directory and
153
+ # store them in a hash
154
+ #
155
+ def project_files
156
+ keep = %r/.rake$|Rakefile$|.patch$|.c$/
157
+ strip_path = %r/\A#{data}?/o
158
+ paths = Hash.new { |h,k| h[k] = [] }
159
+ Find.find( data ) do |path|
160
+ next unless keep =~ path
161
+
162
+ if File.directory?( path ) then
163
+ paths[ path.sub( strip_path, '' ) ]
164
+ next
165
+ end
166
+ dir = ::File.dirname( path ).sub( strip_path, '' )
167
+ paths[dir] << path.sub( strip_path, '' )
168
+ end
169
+
170
+ return paths
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,16 @@
1
+ module Crate
2
+
3
+ # encapsulate a packing list, a list of files and the prefix of those files
4
+ # that need to be stripped off for doing require
5
+ class PackingList
6
+
7
+ attr_reader :prefix
8
+ attr_reader :file_list
9
+
10
+ def initialize( file_list, prefix = Dir.pwd )
11
+ @prefix = prefix
12
+ @file_list = file_list.collect { |f| File.expand_path(f) }
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,255 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+ require 'amalgalite'
4
+ require 'amalgalite/requires'
5
+
6
+ module Crate
7
+ #
8
+ # the Crate top level task, there should only be one of these in existence at
9
+ # a time. This task is accessible via Crate.project, and is what is defined
10
+ # in the Rakefile in the project directory.
11
+ #
12
+ class Project < ::Rake::TaskLib
13
+ # Name of the project
14
+ attr_reader :name
15
+
16
+ # Top level directory of the project.
17
+ attr_reader :project_root
18
+
19
+ # subdirectory of +project_root+ in which the recipe's are stored.
20
+ # default: 'recipes'
21
+ attr_accessor :recipe_dir
22
+
23
+ # subdirectory of +project_root+ where recipes' are built. default: 'build'
24
+ attr_accessor :build_dir
25
+
26
+ # subdirectory of +project_root+ representing a fake installation root.
27
+ # default 'fakeroot'
28
+ attr_accessor :install_dir
29
+
30
+ # the directory where the final products are stored
31
+ attr_accessor :dist_dir
32
+
33
+ # The list of extensions to compile
34
+ attr_reader :extensions
35
+
36
+ # The 'main' file
37
+ attr_accessor :main_file
38
+
39
+ # The 'main' class which is instantiated to start the application
40
+ attr_accessor :main_class
41
+
42
+ # The method on an instance of 'main_class' to invoke with ARGV, ENV
43
+ # arguments to start the crate based program.
44
+ attr_accessor :run_method
45
+
46
+ def initialize( name )
47
+ raise "Crate Project already initialized" if ::Crate.project
48
+ @name = name
49
+ @project_root = File.expand_path( File.dirname( Rake.application.rakefile ) )
50
+ @recipe_dir = File.join( @project_root, 'recipes' )
51
+ @build_dir = File.join( @project_root, 'build' )
52
+ @install_dir = File.join( @project_root, 'fakeroot' )
53
+ @dist_dir = File.join( @project_root, 'dist' )
54
+ yield self if block_given?
55
+ ::Crate.project = self
56
+ define
57
+ end
58
+
59
+ def recipe_dir=( rd )
60
+ @recipe_dir = File.join( project_root, rd )
61
+ end
62
+
63
+ def build_dir=( bd )
64
+ @build_dir = File.join( project_root, bd)
65
+ end
66
+
67
+ def install_dir=( id )
68
+ @install_dir = File.join( project_root, id )
69
+ end
70
+
71
+ def dist_dir=( dd )
72
+ @dist_dir = File.join( project_root, dd )
73
+ end
74
+
75
+ def extensions=( list )
76
+ @extensions = list.select { |l| l.index("#").nil? }
77
+ end
78
+
79
+ # The list of application files to pack into the app.db
80
+ # This is an array of PackingList
81
+ def packing_lists
82
+ @packing_lists ||= []
83
+ end
84
+
85
+ def packing_lists=( list )
86
+ @packing_lists = [ list ].flatten
87
+ end
88
+
89
+ #
90
+ # Create a logger for the project
91
+ #
92
+ def logger
93
+ unless @logger
94
+ @logger = Logging::Logger[name]
95
+ @logger.level = :debug
96
+ @logger.add_appenders
97
+
98
+ @logger.add_appenders(
99
+ Logging::Appenders::File.new( File.join( project_root, "project.log" ), :layout => Logging::Layouts::Pattern.new( :pattern => "%d %5l: %m\n" )),
100
+ Logging::Appenders::Stdout.new( 'stdout', :level => :info,
101
+ :layout => Logging::Layouts::Pattern.new( :pattern => "%d %5l: %m\n",
102
+ :date_pattern => "%H:%M:%S") )
103
+ )
104
+ end
105
+ return @logger
106
+ end
107
+
108
+ #
109
+ # Load upthe compile params we may need to compile the project. This method
110
+ # is usless until after the :ruby task has been completed
111
+ #
112
+ def compile_params
113
+ unless @compile_params
114
+ @compile_params = {}
115
+ Dir.chdir( ::Crate.ruby.pkg_dir ) do
116
+ %w[ CC CFLAGS XCFLAGS LDFLAGS CPPFLAGS LIBS ].each do |p|
117
+ @compile_params[p] = %x( ./miniruby -I. -rrbconfig -e 'puts Config::CONFIG["#{p}"]' ).strip
118
+ end
119
+ end
120
+ end
121
+ return @compile_params
122
+ end
123
+
124
+ #
125
+ # Create the crate_boot.h file
126
+ #
127
+ def create_crate_boot_h
128
+ File.open( "crate_boot.h", "w+" ) do |h|
129
+ h.puts <<-CRATE_BOOT_H
130
+ /**
131
+ * Automatcially generated. Do not edit. To change the contents of
132
+ * this file, update your project main_file, main_class and run_method
133
+ * options and rebuild.
134
+ */
135
+ #define CRATE_MAIN_FILE "#{Crate.project.main_file}"
136
+ #define CRATE_MAIN_CLASS "#{Crate.project.main_class}"
137
+ #define CRATE_RUN_METHOD "#{Crate.project.run_method}"
138
+ CRATE_BOOT_H
139
+ end
140
+ end
141
+
142
+
143
+ #
144
+ # Compile the crate_boot stub to an object file
145
+ #
146
+ def compile_crate_boot
147
+ create_crate_boot_h
148
+ compile_options = %w[ CFLAGS XCFLAGS CPPFLAGS ].collect { |c| compile_params[c] }.join(' ')
149
+ cmd = "#{compile_params['CC']} #{compile_options} -I#{Crate.ruby.pkg_dir} -o crate_boot.o -c crate_boot.c"
150
+ logger.debug cmd
151
+ sh cmd
152
+ ::CLEAN << "crate_boot.o"
153
+ end
154
+
155
+ #
156
+ # Run the link command to create the final executable
157
+ #
158
+ def link_project
159
+ link_options = %w[ CFLAGS XCFLAGS LDFLAGS ].collect { |c| compile_params[c] }.join(' ')
160
+ Dir.chdir( ::Crate.ruby.pkg_dir ) do
161
+ dot_a = FileList[ "**/*.a" ]
162
+ dot_o = [ "ext/extinit.o", File.join( project_root, "crate_boot.o" )]
163
+ libs = compile_params['LIBS']
164
+ cmd = "#{compile_params['CC']} #{link_options} #{dot_o.join(' ')} #{dot_a.join(' ')} -o #{File.join( dist_dir, name) }"
165
+ logger.debug cmd
166
+ sh cmd
167
+ end
168
+ end
169
+
170
+ #
171
+ # define the project task
172
+ #
173
+ def define
174
+ lib_db = File.join( dist_dir, "lib.db" )
175
+ app_db = File.join( dist_dir, "app.db" )
176
+ directory dist_dir
177
+ packer_cmd = "~/Projects/amalgalite/bin/amalgalite-pack"
178
+
179
+ task :pack_ruby => dist_dir do
180
+ prefix = File.join( ::Crate.ruby.pkg_dir, "lib" )
181
+
182
+ logger.info "Packing ruby standard lib into #{lib_db}"
183
+ cmd = "#{packer_cmd} --drop-table --db #{lib_db} --compress --strip-prefix #{prefix} #{prefix}"
184
+ logger.debug cmd
185
+ sh "#{cmd} > /dev/null"
186
+ end
187
+
188
+ task :pack_ruby_ext => dist_dir do
189
+ logger.info "Packing ruby extension libs into #{lib_db}"
190
+ File.open( ::Crate.ruby.ext_setup_file ) do |f|
191
+ f.each_line do |line|
192
+ next if line =~ /\Aoption/
193
+ next if line.strip.length == 0
194
+ next if line =~ /\A#/
195
+
196
+ prefix = File.join( ::Crate.ruby.ext_dir, line.strip, "lib" )
197
+ next unless File.directory?( prefix )
198
+
199
+ cmd = "#{packer_cmd} --merge --db #{lib_db} --compress --strip-prefix #{prefix} #{prefix}"
200
+ logger.debug cmd
201
+ sh "#{cmd} > /dev/null"
202
+ end
203
+ end
204
+ end
205
+
206
+ task :pack_amalgalite => dist_dir do
207
+ logger.info "Packing amalgalite into #{lib_db}"
208
+ cmd = "#{packer_cmd} --drop-table --db #{lib_db} --self"
209
+ logger.debug cmd
210
+ sh "#{cmd} > /dev/null"
211
+ end
212
+
213
+ task :pack_app => [ :pack_amalgalite, :pack_ruby, :pack_ruby_ext ] do
214
+ logger.info "Packing project packing lists lists into #{app_db}"
215
+ Crate.project.packing_lists.each_with_index do |pl,idx|
216
+ pc = ( idx == 0 ) ? "#{packer_cmd} --drop-table" : packer_cmd.dup
217
+ cmd = "#{pc} --db #{app_db} --merge --compress --strip-prefix #{pl.prefix} #{pl.file_list.join(' ')} "
218
+ logger.debug cmd
219
+ sh "#{cmd} > /dev/null"
220
+ end
221
+ end
222
+
223
+ file "crate_boot.o" => "crate_boot.c" do
224
+ compile_crate_boot
225
+ end
226
+
227
+ app_path = File.join( dist_dir, name )
228
+ file app_path => [ "crate_boot.o", dist_dir ] do
229
+ link_project
230
+ end
231
+
232
+ desc "Build #{name}"
233
+ #task :default => [ :ruby ] do
234
+ task :default => [ app_path, :pack_app ] do
235
+ logger.info "Build #{name}"
236
+ compile_crate_boot
237
+ link_project
238
+ end
239
+ ::CLEAN << self.install_dir
240
+ ::CLEAN << "project.log"
241
+ ::CLEAN << self.dist_dir
242
+ load_rakefiles
243
+ end
244
+
245
+ #
246
+ # Load all .rake files that are in a recipe sub directory
247
+ #
248
+ def load_rakefiles
249
+ Dir["#{recipe_dir}/*/*.rake"].each do |recipe|
250
+ logger.debug "loading #{recipe}"
251
+ import recipe
252
+ end
253
+ end
254
+ end
255
+ end