mini_portile2 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
data/appveyor.yml ADDED
@@ -0,0 +1,24 @@
1
+ ---
2
+ install:
3
+ - ps: ((New-Object Net.WebClient).DownloadFile('https://raw.githubusercontent.com/bagder/ca-bundle/master/ca-bundle.crt', "$env:TMP\ca-bundle.crt"))
4
+ - SET SSL_CERT_FILE=%TMP%\ca-bundle.crt
5
+ - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
6
+ - SET RAKEOPT=-rdevkit
7
+ - ruby --version
8
+ - gem --version
9
+ - bundle install
10
+
11
+ build: off
12
+
13
+ test_script:
14
+ - bundle exec rake
15
+
16
+ environment:
17
+ matrix:
18
+ - ruby_version: "22-x64"
19
+ - ruby_version: "22"
20
+ - ruby_version: "21-x64"
21
+ - ruby_version: "21"
22
+ - ruby_version: "200-x64"
23
+ - ruby_version: "200"
24
+ - ruby_version: "193"
@@ -0,0 +1,2 @@
1
+ ports
2
+ tmp
data/examples/Rakefile ADDED
@@ -0,0 +1,127 @@
1
+ require 'rbconfig'
2
+
3
+ $: << File.expand_path(File.join(File.dirname(__FILE__), "../lib"))
4
+ require "mini_portile2"
5
+
6
+ recipes = []
7
+
8
+ def windows?
9
+ RbConfig::CONFIG['target_os'] =~ /mswin|mingw32/
10
+ end
11
+
12
+ # libiconv
13
+ libiconv = MiniPortile.new "libiconv", "1.14"
14
+ libiconv.files << "ftp://ftp.gnu.org/pub/gnu/#{libiconv.name}/#{libiconv.name}-#{libiconv.version}.tar.gz"
15
+ unless windows?
16
+ libiconv.patch_files = Dir["libiconv-patches/*.patch"].map { |dir| File.expand_path dir }
17
+ end
18
+
19
+ recipes.push libiconv
20
+
21
+ # sqlite3
22
+ sqlite3 = MiniPortile.new "sqlite3", "3.8.4.1"
23
+ sqlite3.files << "http://sqlite.org/2014/sqlite-autoconf-3080401.tar.gz"
24
+
25
+ recipes.push sqlite3
26
+
27
+ # c-ares
28
+ c_ares = MiniPortile.new "c-ares", "1.7.5"
29
+ c_ares.files << "http://distfiles.openknapsack.org/#{c_ares.name}/#{c_ares.name}-#{c_ares.version}.tar.gz"
30
+
31
+ recipes.push c_ares
32
+
33
+ # zlib
34
+ class ZlibRecipe < MiniPortile
35
+ def windows?
36
+ !(host =~ /mswin|mingw/).nil?
37
+ end
38
+
39
+ def configure
40
+ return super unless windows?
41
+
42
+ Dir.chdir work_path do
43
+ mk = File.read 'win32/Makefile.gcc'
44
+ File.open 'win32/Makefile.gcc', 'wb' do |f|
45
+ f.puts "BINARY_PATH = #{path}/bin"
46
+ f.puts "LIBRARY_PATH = #{path}/lib"
47
+ f.puts "INCLUDE_PATH = #{path}/include"
48
+
49
+ cross_build? and
50
+ mk.sub!(/^PREFIX\s*=\s*$/, "PREFIX = #{host}-")
51
+
52
+ f.puts mk
53
+ end
54
+ end
55
+ end
56
+
57
+ def configure_defaults
58
+ ["--static"]
59
+ end
60
+
61
+ def configured?
62
+ return super unless windows?
63
+
64
+ !!(File.read(File.join(work_path, 'win32/Makefile.gcc')) =~ /^BINARY_PATH/)
65
+ end
66
+
67
+ def compile
68
+ return super unless windows?
69
+
70
+ execute "compile", "make -f win32/Makefile.gcc"
71
+ end
72
+
73
+ def install
74
+ return if installed?
75
+ return super unless windows?
76
+
77
+ execute "install", %Q(#{make_cmd} -f win32/Makefile.gcc install)
78
+ end
79
+
80
+ def cross_build?
81
+ host != original_host
82
+ end
83
+ end
84
+
85
+ zlib = ZlibRecipe.new "zlib", "1.2.8"
86
+ zlib.files << {
87
+ url: "http://zlib.net/#{zlib.name}-#{zlib.version}.tar.gz",
88
+ md5: "44d667c142d7cda120332623eab69f40",
89
+ }
90
+
91
+
92
+ recipes.push zlib
93
+
94
+ namespace :ports do
95
+ directory "ports"
96
+
97
+ recipes.each do |recipe|
98
+ desc "Install port #{recipe.name} #{recipe.version}"
99
+ task recipe.name => ["ports"] do |t|
100
+ checkpoint = "ports/.#{recipe.name}-#{recipe.version}-#{recipe.host}.installed"
101
+
102
+ unless File.exist?(checkpoint)
103
+ recipe.cook
104
+ touch checkpoint
105
+ end
106
+
107
+ recipe.activate
108
+ end
109
+
110
+ task :all => recipe.name
111
+ end
112
+
113
+ desc "Install all ports and display installation location"
114
+ task :all do
115
+ recipes.each do |recipe|
116
+ puts "Artifacts of '#{recipe.name}' in '#{recipe.path}'"
117
+ end
118
+ puts "LDFLAGS: " << ENV['LDFLAGS'].inspect
119
+ end
120
+ end
121
+
122
+ desc "Adjust all recipes host for cross-compilation"
123
+ task :cross do
124
+ recipes.each do |recipe|
125
+ recipe.host = "i686-w64-mingw32"
126
+ end
127
+ end
@@ -0,0 +1,16 @@
1
+ This file tests the 'patch' functionality, as well as working around a
2
+ libiconv compilation issue with glibc >= 2.16.
3
+
4
+ --- a/srclib/stdio.in.h 2015-08-23 13:59:57.395880263 -0400
5
+ +++ b/srclib/stdio.in.h 2015-08-23 14:00:00.047880153 -0400
6
+ @@ -695,8 +695,10 @@
7
+ /* It is very rare that the developer ever has full control of stdin,
8
+ so any use of gets warrants an unconditional warning. Assume it is
9
+ always declared, since it is required by C89. */
10
+ +#if defined(__GLIBC__) && !defined(__UCLIBC__) && !__GLIBC_PREREQ(2, 16)
11
+ _GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
12
+ #endif
13
+ +#endif
14
+
15
+
16
+ #if @GNULIB_OBSTACK_PRINTF@ || @GNULIB_OBSTACK_PRINTF_POSIX@
@@ -0,0 +1,2 @@
1
+ require "mini_portile2/version"
2
+ require "mini_portile2/mini_portile"
@@ -0,0 +1,495 @@
1
+ require 'rbconfig'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'net/ftp'
5
+ require 'fileutils'
6
+ require 'tempfile'
7
+ require 'digest/md5'
8
+ require 'open-uri'
9
+ require 'cgi'
10
+ require 'rbconfig'
11
+ require 'shellwords'
12
+
13
+ # Monkey patch for Net::HTTP by ruby open-uri fix:
14
+ # https://github.com/ruby/ruby/commit/58835a9
15
+ class Net::HTTP
16
+ private
17
+ remove_method(:edit_path)
18
+ def edit_path(path)
19
+ if proxy?
20
+ if path.start_with?("ftp://") || use_ssl?
21
+ path
22
+ else
23
+ "http://#{addr_port}#{path}"
24
+ end
25
+ else
26
+ path
27
+ end
28
+ end
29
+ end
30
+
31
+ class MiniPortile
32
+ attr_reader :name, :version, :original_host
33
+ attr_writer :configure_options
34
+ attr_accessor :host, :files, :patch_files, :target, :logger
35
+
36
+ def initialize(name, version)
37
+ @name = name
38
+ @version = version
39
+ @target = 'ports'
40
+ @files = []
41
+ @patch_files = []
42
+ @log_files = {}
43
+ @logger = STDOUT
44
+
45
+ @original_host = @host = detect_host
46
+ end
47
+
48
+ def download
49
+ files_hashs.each do |file|
50
+ download_file(file[:url], file[:local_path])
51
+ verify_file(file)
52
+ end
53
+ end
54
+
55
+ def extract
56
+ files_hashs.each do |file|
57
+ extract_file(file[:local_path], tmp_path)
58
+ end
59
+ end
60
+
61
+ def apply_patch(patch_file)
62
+ (
63
+ # Not a class variable because closures will capture self.
64
+ @apply_patch ||=
65
+ case
66
+ when which('git')
67
+ lambda { |file|
68
+ message "Running git apply with #{file}... "
69
+ # By --work-tree=. git-apply uses the current directory as
70
+ # the project root and will not search upwards for .git.
71
+ execute('patch', ["git", "--work-tree=.", "apply", file], :initial_message => false)
72
+ }
73
+ when which('patch')
74
+ lambda { |file|
75
+ message "Running patch with #{file}... "
76
+ execute('patch', ["patch", "-p1", "-i", file], :initial_message => false)
77
+ }
78
+ else
79
+ raise "Failed to complete patch task; patch(1) or git(1) is required."
80
+ end
81
+ ).call(patch_file)
82
+ end
83
+
84
+ def patch
85
+ @patch_files.each do |full_path|
86
+ next unless File.exist?(full_path)
87
+ apply_patch(full_path)
88
+ end
89
+ end
90
+
91
+ def configure_options
92
+ @configure_options ||= configure_defaults
93
+ end
94
+
95
+ def configure
96
+ return if configured?
97
+
98
+ md5_file = File.join(tmp_path, 'configure.md5')
99
+ digest = Digest::MD5.hexdigest(computed_options.to_s)
100
+ File.open(md5_file, "w") { |f| f.write digest }
101
+
102
+ execute('configure', %w(sh configure) + computed_options)
103
+ end
104
+
105
+ def compile
106
+ execute('compile', make_cmd)
107
+ end
108
+
109
+ def install
110
+ return if installed?
111
+ execute('install', %Q(#{make_cmd} install))
112
+ end
113
+
114
+ def downloaded?
115
+ missing = files_hashs.detect do |file|
116
+ !File.exist?(file[:local_path])
117
+ end
118
+
119
+ missing ? false : true
120
+ end
121
+
122
+ def configured?
123
+ configure = File.join(work_path, 'configure')
124
+ makefile = File.join(work_path, 'Makefile')
125
+ md5_file = File.join(tmp_path, 'configure.md5')
126
+
127
+ stored_md5 = File.exist?(md5_file) ? File.read(md5_file) : ""
128
+ current_md5 = Digest::MD5.hexdigest(computed_options.to_s)
129
+
130
+ (current_md5 == stored_md5) && newer?(makefile, configure)
131
+ end
132
+
133
+ def installed?
134
+ makefile = File.join(work_path, 'Makefile')
135
+ target_dir = Dir.glob("#{port_path}/*").find { |d| File.directory?(d) }
136
+
137
+ newer?(target_dir, makefile)
138
+ end
139
+
140
+ def cook
141
+ download unless downloaded?
142
+ extract
143
+ patch
144
+ configure unless configured?
145
+ compile
146
+ install unless installed?
147
+
148
+ return true
149
+ end
150
+
151
+ def activate
152
+ lib_path = File.join(port_path, "lib")
153
+ vars = {
154
+ 'PATH' => File.join(port_path, 'bin'),
155
+ 'CPATH' => File.join(port_path, 'include'),
156
+ 'LIBRARY_PATH' => lib_path
157
+ }.reject { |env, path| !File.directory?(path) }
158
+
159
+ output "Activating #{@name} #{@version} (from #{port_path})..."
160
+ vars.each do |var, path|
161
+ full_path = File.expand_path(path)
162
+
163
+ # turn into a valid Windows path (if required)
164
+ full_path.gsub!(File::SEPARATOR, File::ALT_SEPARATOR) if File::ALT_SEPARATOR
165
+
166
+ # save current variable value
167
+ old_value = ENV[var] || ''
168
+
169
+ unless old_value.include?(full_path)
170
+ ENV[var] = "#{full_path}#{File::PATH_SEPARATOR}#{old_value}"
171
+ end
172
+ end
173
+
174
+ # rely on LDFLAGS when cross-compiling
175
+ if File.exist?(lib_path) && (@host != @original_host)
176
+ full_path = File.expand_path(lib_path)
177
+
178
+ old_value = ENV.fetch("LDFLAGS", "")
179
+
180
+ unless old_value.include?(full_path)
181
+ ENV["LDFLAGS"] = "-L#{full_path} #{old_value}".strip
182
+ end
183
+ end
184
+ end
185
+
186
+ def path
187
+ File.expand_path(port_path)
188
+ end
189
+
190
+ private
191
+
192
+ def tmp_path
193
+ "tmp/#{@host}/ports/#{@name}/#{@version}"
194
+ end
195
+
196
+ def port_path
197
+ "#{@target}/#{@host}/#{@name}/#{@version}"
198
+ end
199
+
200
+ def archives_path
201
+ "#{@target}/archives"
202
+ end
203
+
204
+ def work_path
205
+ Dir.glob("#{tmp_path}/*").find { |d| File.directory?(d) }
206
+ end
207
+
208
+ def configure_defaults
209
+ [
210
+ "--host=#{@host}", # build for specific target (host)
211
+ "--enable-static", # build static library
212
+ "--disable-shared" # disable generation of shared object
213
+ ]
214
+ end
215
+
216
+ def configure_prefix
217
+ "--prefix=#{File.expand_path(port_path)}"
218
+ end
219
+
220
+ def computed_options
221
+ [
222
+ configure_options, # customized or default options
223
+ configure_prefix, # installation target
224
+ ].flatten
225
+ end
226
+
227
+ def files_hashs
228
+ @files.map do |file|
229
+ hash = case file
230
+ when String
231
+ { :url => file }
232
+ when Hash
233
+ file.dup
234
+ else
235
+ raise ArgumentError, "files must be an Array of Stings or Hashs"
236
+ end
237
+
238
+ url = hash.fetch(:url){ raise ArgumentError, "no url given" }
239
+ filename = File.basename(url)
240
+ hash[:local_path] = File.join(archives_path, filename)
241
+ hash
242
+ end
243
+ end
244
+
245
+ def verify_file(file)
246
+ digest = case
247
+ when exp=file[:sha256] then Digest::SHA256
248
+ when exp=file[:sha1] then Digest::SHA1
249
+ when exp=file[:md5] then Digest::MD5
250
+ end
251
+ if digest
252
+ is = digest.file(file[:local_path]).hexdigest
253
+ unless is == exp.downcase
254
+ raise "Downloaded file '#{file[:local_path]}' has wrong hash: expected: #{exp} is: #{is}"
255
+ end
256
+ end
257
+ end
258
+
259
+ def log_file(action)
260
+ @log_files[action] ||=
261
+ File.expand_path("#{action}.log", tmp_path).tap { |file|
262
+ File.unlink(file) if File.exist?(file)
263
+ }
264
+ end
265
+
266
+ def tar_exe
267
+ @@tar_exe ||= begin
268
+ %w[gtar bsdtar tar basic-bsdtar].find { |c|
269
+ which(c)
270
+ }
271
+ end
272
+ end
273
+
274
+ def tar_compression_switch(filename)
275
+ case File.extname(filename)
276
+ when '.gz', '.tgz'
277
+ 'z'
278
+ when '.bz2', '.tbz2'
279
+ 'j'
280
+ when '.Z'
281
+ 'Z'
282
+ else
283
+ ''
284
+ end
285
+ end
286
+
287
+ # From: http://stackoverflow.com/a/5471032/7672
288
+ # Thanks, Mislav!
289
+ #
290
+ # Cross-platform way of finding an executable in the $PATH.
291
+ #
292
+ # which('ruby') #=> /usr/bin/ruby
293
+ def which(cmd)
294
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
295
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
296
+ exts.each { |ext|
297
+ exe = File.join(path, "#{cmd}#{ext}")
298
+ return exe if File.executable? exe
299
+ }
300
+ end
301
+ return nil
302
+ end
303
+
304
+ def detect_host
305
+ return @detect_host if defined?(@detect_host)
306
+
307
+ begin
308
+ ENV["LC_ALL"], old_lc_all = "C", ENV["LC_ALL"]
309
+
310
+ output = `#{gcc_cmd} -v 2>&1`
311
+ if m = output.match(/^Target\: (.*)$/)
312
+ @detect_host = m[1]
313
+ end
314
+
315
+ @detect_host
316
+ ensure
317
+ ENV["LC_ALL"] = old_lc_all
318
+ end
319
+ end
320
+
321
+ def extract_file(file, target)
322
+ filename = File.basename(file)
323
+ FileUtils.mkdir_p target
324
+
325
+ message "Extracting #{filename} into #{target}... "
326
+ execute('extract', [tar_exe, "#{tar_compression_switch(filename)}xf", file, "-C", target], {:cd => Dir.pwd, :initial_message => false})
327
+ end
328
+
329
+ def execute(action, command, options={})
330
+ log_out = log_file(action)
331
+
332
+ Dir.chdir (options.fetch(:cd){ work_path }) do
333
+ if options.fetch(:initial_message){ true }
334
+ message "Running '#{action}' for #{@name} #{@version}... "
335
+ end
336
+
337
+ if Process.respond_to?(:spawn) && ! RbConfig.respond_to?(:java)
338
+ args = [command].flatten + [{[:out, :err]=>[log_out, "a"]}]
339
+ pid = spawn(*args)
340
+ Process.wait(pid)
341
+ else
342
+ redirected = if command.kind_of?(Array)
343
+ %Q{#{command.map(&:shellescape).join(" ")} > #{log_out.shellescape} 2>&1}
344
+ else
345
+ %Q{#{command} > #{log_out.shellescape} 2>&1}
346
+ end
347
+ system redirected
348
+ end
349
+
350
+ if $?.success?
351
+ output "OK"
352
+ return true
353
+ else
354
+ if File.exist? log_out
355
+ output "ERROR, review '#{log_out}' to see what happened. Last lines are:"
356
+ output("=" * 72)
357
+ log_lines = File.readlines(log_out)
358
+ output(log_lines[-[log_lines.length, 20].min .. -1])
359
+ output("=" * 72)
360
+ end
361
+ raise "Failed to complete #{action} task"
362
+ end
363
+ end
364
+ end
365
+
366
+ def newer?(target, checkpoint)
367
+ if (target && File.exist?(target)) && (checkpoint && File.exist?(checkpoint))
368
+ File.mtime(target) > File.mtime(checkpoint)
369
+ else
370
+ false
371
+ end
372
+ end
373
+
374
+ # print out a message with the logger
375
+ def message(text)
376
+ @logger.print text
377
+ @logger.flush
378
+ end
379
+
380
+ # print out a message using the logger but return to a new line
381
+ def output(text = "")
382
+ @logger.puts text
383
+ @logger.flush
384
+ end
385
+
386
+ # Slighly modified from RubyInstaller uri_ext, Rubinius configure
387
+ # and adaptations of Wayne's RailsInstaller
388
+ def download_file(url, full_path, count = 3)
389
+ return if File.exist?(full_path)
390
+ uri = URI.parse(url)
391
+ begin
392
+ case uri.scheme.downcase
393
+ when /ftp/
394
+ download_file_ftp(uri, full_path)
395
+ when /http|https/
396
+ download_file_http(url, full_path, count)
397
+ end
398
+ rescue Exception => e
399
+ File.unlink full_path if File.exist?(full_path)
400
+ output "ERROR: #{e.message}"
401
+ raise "Failed to complete download task"
402
+ end
403
+ end
404
+
405
+ def download_file_http(url, full_path, count = 3)
406
+ filename = File.basename(full_path)
407
+ with_tempfile(filename, full_path) do |temp_file|
408
+ progress = 0
409
+ total = 0
410
+ params = {
411
+ "Accept-Encoding" => 'identity',
412
+ :content_length_proc => lambda{|length| total = length },
413
+ :progress_proc => lambda{|bytes|
414
+ new_progress = (bytes * 100) / total
415
+ message "\rDownloading %s (%3d%%) " % [filename, new_progress]
416
+ progress = new_progress
417
+ }
418
+ }
419
+ proxy_uri = URI.parse(url).scheme.downcase == 'https' ?
420
+ ENV["https_proxy"] :
421
+ ENV["http_proxy"]
422
+ if proxy_uri
423
+ _, userinfo, _p_host, _p_port = URI.split(proxy_uri)
424
+ if userinfo
425
+ proxy_user, proxy_pass = userinfo.split(/:/).map{|s| CGI.unescape(s) }
426
+ params[:proxy_http_basic_authentication] =
427
+ [proxy_uri, proxy_user, proxy_pass]
428
+ end
429
+ end
430
+ begin
431
+ OpenURI.open_uri(url, 'rb', params) do |io|
432
+ temp_file << io.read
433
+ end
434
+ output
435
+ rescue OpenURI::HTTPRedirect => redirect
436
+ raise "Too many redirections for the original URL, halting." if count <= 0
437
+ count = count - 1
438
+ return download_file(redirect.url, full_path, count - 1)
439
+ rescue => e
440
+ output e.message
441
+ return false
442
+ end
443
+ end
444
+ end
445
+
446
+ def download_file_ftp(uri, full_path)
447
+ filename = File.basename(uri.path)
448
+ with_tempfile(filename, full_path) do |temp_file|
449
+ progress = 0
450
+ total = 0
451
+ params = {
452
+ :content_length_proc => lambda{|length| total = length },
453
+ :progress_proc => lambda{|bytes|
454
+ new_progress = (bytes * 100) / total
455
+ message "\rDownloading %s (%3d%%) " % [filename, new_progress]
456
+ progress = new_progress
457
+ }
458
+ }
459
+ if ENV["ftp_proxy"]
460
+ _, userinfo, _p_host, _p_port = URI.split(ENV['ftp_proxy'])
461
+ if userinfo
462
+ proxy_user, proxy_pass = userinfo.split(/:/).map{|s| CGI.unescape(s) }
463
+ params[:proxy_http_basic_authentication] =
464
+ [ENV['ftp_proxy'], proxy_user, proxy_pass]
465
+ end
466
+ end
467
+ OpenURI.open_uri(uri, 'rb', params) do |io|
468
+ temp_file << io.read
469
+ end
470
+ output
471
+ end
472
+ rescue Net::FTPError
473
+ return false
474
+ end
475
+
476
+ def with_tempfile(filename, full_path)
477
+ temp_file = Tempfile.new("download-#{filename}")
478
+ temp_file.binmode
479
+ yield temp_file
480
+ temp_file.close
481
+ File.unlink full_path if File.exist?(full_path)
482
+ FileUtils.mkdir_p File.dirname(full_path)
483
+ FileUtils.mv temp_file.path, full_path, :force => true
484
+ end
485
+
486
+ def gcc_cmd
487
+ cc = ENV["CC"] || RbConfig::CONFIG["CC"] || "gcc"
488
+ return cc.dup
489
+ end
490
+
491
+ def make_cmd
492
+ m = ENV['MAKE'] || ENV['make'] || 'make'
493
+ return m.dup
494
+ end
495
+ end