crate 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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