tara 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 99f4bc434bfa007f054a82265445cd9846937302
4
+ data.tar.gz: 270a01f1559d5747ecc0d50834ba1b0b98336a39
5
+ SHA512:
6
+ metadata.gz: dc93d8fc594db3d1c1f0a415166f3ac5b9151034b7f7d7fefff32b8f244c882a93c64709878b2f8d392659018592710a160457402efaafdd3b3e6819d2b43192
7
+ data.tar.gz: 156522d2729c5ca0ffe381d183030add5dd930ccda05eeb07d4efaae8cc4af147969b0397f4788231efdd694e8ec22c97a165adc33eec2a868ff4f0fa131bc8b
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'tara'
6
+
7
+
8
+ exit(Tara::Cli.new(ARGV).run)
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ require 'bundler'
4
+ require 'pathname'
5
+ require 'tmpdir'
6
+
7
+
8
+ module Tara
9
+ TaraError = Class.new(StandardError)
10
+ ExecError = Class.new(TaraError)
11
+ NotFoundError = Class.new(TaraError)
12
+ TooManyRedirectsError = Class.new(TaraError)
13
+ UnknownResponseError = Class.new(TaraError)
14
+ end
15
+
16
+ require 'tara/archive'
17
+ require 'tara/archiver'
18
+ require 'tara/cli'
19
+ require 'tara/executable'
20
+ require 'tara/fetcher'
21
+ require 'tara/installer'
22
+ require 'tara/shell'
@@ -0,0 +1,90 @@
1
+ # encoding: utf-8
2
+
3
+ module Tara
4
+ class Archive
5
+ def initialize(config={})
6
+ @config = config
7
+ @config[:app_dir] ||= Dir.pwd
8
+ @config[:app_name] ||= File.basename(@config[:app_dir])
9
+ @config[:build_dir] ||= File.join(@config[:app_dir], 'build')
10
+ @config[:target_dir] ||= @config[:build_dir]
11
+ @config[:download_dir] ||= File.join(@config[:build_dir], 'downloads')
12
+ @config[:archive_name] ||= @config[:app_name] + '.tgz'
13
+ @config[:files] ||= %w[lib/**/*.rb]
14
+ @config[:executables] ||= %w[bin/*]
15
+ @config[:target] ||= 'linux-x86_64'
16
+ @config[:traveling_ruby_version] ||= '20150210'
17
+ @config[:without_groups] ||= %w[development test]
18
+ end
19
+
20
+ def self.create(config={})
21
+ new(config).create
22
+ end
23
+
24
+ def create
25
+ Dir.mktmpdir do |tmp_dir|
26
+ project_dir = Pathname.new(@config[:app_dir])
27
+ package_dir = Pathname.new(tmp_dir)
28
+ target_dir = Pathname.new(@config[:target_dir])
29
+ install_dependencies(package_dir, fetcher)
30
+ copy_source(project_dir, package_dir)
31
+ copy_executables(project_dir, package_dir)
32
+ Dir.chdir(tmp_dir) do
33
+ create_archive(target_dir)
34
+ end
35
+ File.join(target_dir, @config[:archive_name])
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ DOT_PATH = Pathname.new('.')
42
+
43
+ def copy_source(project_dir, package_dir)
44
+ @config[:files].each do |glob_string|
45
+ Pathname.glob(project_dir.join(glob_string)).each do |file|
46
+ copy_file(project_dir, package_dir, file)
47
+ end
48
+ end
49
+ end
50
+
51
+ def copy_executables(project_dir, package_dir)
52
+ @config[:executables].each do |executable_glob|
53
+ Pathname.glob(project_dir.join(executable_glob)).each do |executable|
54
+ copy_file(project_dir, package_dir, executable)
55
+ FileUtils.chmod(0755, package_dir.join(executable))
56
+ create_exec_wrapper(package_dir, executable)
57
+ end
58
+ end
59
+ end
60
+
61
+ def create_archive(target_dir)
62
+ Shell.exec('tar -czf %s %s' % [@config[:archive_name], Dir['*'].join(' ')])
63
+ FileUtils.mkdir_p(target_dir)
64
+ FileUtils.cp(@config[:archive_name], target_dir)
65
+ end
66
+
67
+ def fetcher
68
+ @fetcher ||= Fetcher.new(@config[:download_dir], @config[:target], @config[:traveling_ruby_version], @config).setup
69
+ end
70
+
71
+ def install_dependencies(package_dir, fetcher)
72
+ Installer.new(package_dir, fetcher, @config).execute
73
+ end
74
+
75
+ def copy_file(project_dir, package_dir, file)
76
+ rel_file = file.relative_path_from(project_dir)
77
+ unless rel_file.dirname == DOT_PATH
78
+ FileUtils.mkdir_p(package_dir.join(rel_file.dirname))
79
+ end
80
+ FileUtils.cp(project_dir.join(rel_file), package_dir.join(rel_file))
81
+ end
82
+
83
+ def create_exec_wrapper(package_dir, executable)
84
+ wrapper_path = package_dir.join(executable.basename)
85
+ ex = Executable.new(executable.basename)
86
+ File.open(wrapper_path, 'w') { |f| ex.write(f) }
87
+ FileUtils.chmod(0755, wrapper_path)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ module Tara
4
+ class Archiver
5
+ def initialize(options={})
6
+ @options = options
7
+ end
8
+
9
+ def create(options={})
10
+ Archive.create(@options.merge(options))
11
+ end
12
+
13
+ def extension
14
+ @options[:extension] || 'tgz'
15
+ end
16
+
17
+ def content_type
18
+ 'application/x-gzip'
19
+ end
20
+
21
+ def metadata
22
+ @options[:metadata] || {}
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+
3
+ require 'optparse'
4
+
5
+
6
+ module Tara
7
+ class Cli
8
+ def initialize(argv=ARGV, io=$stderr)
9
+ @argv = argv
10
+ @io = io
11
+ end
12
+
13
+ def run
14
+ Archive.new(parse_argv).create
15
+ 0
16
+ rescue => e
17
+ @io.puts(%(Error during packaging: #{e.message} (#{e.class})))
18
+ 1
19
+ end
20
+
21
+ private
22
+
23
+ def parse_argv(options={})
24
+ parser = OptionParser.new do |opts|
25
+ opts.on('--app-name NAME', 'Name of the app') do |app_name|
26
+ options[:app_name] = app_name
27
+ end
28
+
29
+ opts.on('--app-dir APP_DIR', 'Root directory of the app') do |app_dir|
30
+ options[:app_dir] = app_dir
31
+ end
32
+
33
+ opts.on('--download-dir DOWNLOAD_DIR', 'Where to store Traveling Ruby archives') do |download_dir|
34
+ options[:download_dir] = download_dir
35
+ end
36
+
37
+ opts.on('--target TARGET', 'Target platform for archive') do |target|
38
+ options[:target] = target
39
+ end
40
+ end
41
+ parser.parse(@argv)
42
+ options
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ module Tara
4
+ class Executable
5
+ def initialize(name)
6
+ @name = name
7
+ end
8
+
9
+ def write(io)
10
+ io.puts(script(@name))
11
+ end
12
+
13
+ private
14
+
15
+ def script(name)
16
+ <<-EOH.gsub(/^\s+/, '')
17
+ #!/bin/bash
18
+ set -e
19
+ SELF_DIR=$(dirname "$0")
20
+ export BUNDLE_GEMFILE="$SELF_DIR/lib/vendor/Gemfile"
21
+ unset BUNDLE_IGNORE_CONFIG
22
+ exec "$SELF_DIR/lib/ruby/bin/ruby" -rbundler/setup "$SELF_DIR/bin/#{name}" "$@"
23
+ EOH
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,72 @@
1
+ # encoding: utf-8
2
+
3
+ require 'net/http'
4
+
5
+
6
+ module Tara
7
+ class Fetcher
8
+ def initialize(download_dir, target, tr_version, options={})
9
+ @download_dir = download_dir
10
+ @target = target
11
+ @tr_version = tr_version
12
+ @ruby_version = options[:ruby_version] || RUBY_VERSION
13
+ @release_url = options[:tr_release_url] || 'http://d6r77u77i8pq3.cloudfront.net/releases'
14
+ @shell = options[:shell] || Shell
15
+ end
16
+
17
+ def setup
18
+ FileUtils.mkdir_p(@download_dir)
19
+ self
20
+ end
21
+
22
+ def fetch_ruby
23
+ local_uri = %(#{@download_dir}/ruby-#{@tr_version}-#{@ruby_version}-#{@target}.tar.gz)
24
+ fetch(ruby_remote_uri, local_uri)
25
+ end
26
+
27
+ def fetch_native_gem(name, version)
28
+ remote_uri = native_gem_remote_uri(name, version)
29
+ local_uri = %(#{@download_dir}/#{name}-#{version}-#{@tr_version}-#{@ruby_version}-#{@target}.tar.gz)
30
+ fetch(remote_uri, local_uri)
31
+ end
32
+
33
+ private
34
+
35
+ def ruby_remote_uri
36
+ @ruby_remote_uri ||= [@release_url, %(traveling-ruby-#{@tr_version}-#{@ruby_version}-#{@target}.tar.gz)].join('/')
37
+ end
38
+
39
+ def native_gem_remote_uri(name, version)
40
+ [@release_url, %(traveling-ruby-gems-#{@tr_version}-#{@ruby_version}-#{@target}/#{name}-#{version}.tar.gz)].join('/')
41
+ end
42
+
43
+ def fetch(remote_uri, local_uri, limit=10)
44
+ unless File.exist?(local_uri)
45
+ uri = URI(remote_uri)
46
+ Net::HTTP.start(uri.host, uri.port) do |http|
47
+ http.request(Net::HTTP::Get.new(uri)) do |response|
48
+ case response
49
+ when Net::HTTPSuccess
50
+ File.open(local_uri, 'w') do |f|
51
+ response.read_body do |chunk|
52
+ f.write(chunk)
53
+ end
54
+ end
55
+ when Net::HTTPRedirection
56
+ if limit > 0
57
+ fetch(response['location'], local_uri, limit - 1)
58
+ else
59
+ raise TooManyRedirectsError, %(Exhausted redirect limit, ended up at #{remote_uri})
60
+ end
61
+ when Net::HTTPNotFound
62
+ raise NotFoundError, %(#{remote_uri} doesn't exist)
63
+ else
64
+ raise UnknownResponseError, %(#{response.code} '#{response.body}' returned when fetching #{remote_uri})
65
+ end
66
+ end
67
+ end
68
+ end
69
+ local_uri
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,160 @@
1
+ # encoding: utf-8
2
+
3
+ module Tara
4
+ class Installer
5
+ def initialize(package_dir, fetcher, options={})
6
+ @package_dir = package_dir
7
+ @fetcher = fetcher
8
+ @without_groups = options[:without_groups]
9
+ @app_dir = Pathname.new(options[:app_dir])
10
+ @shell = options[:shell] || Shell
11
+ end
12
+
13
+ def execute
14
+ bundle_gems
15
+ extract_ruby
16
+ extract_native_gems
17
+ strip_tests
18
+ strip_docs
19
+ strip_leftovers
20
+ strip_java_files
21
+ strip_git_files
22
+ create_bundler_config
23
+ end
24
+
25
+ private
26
+
27
+ def bundler_command
28
+ @bundler_command ||= begin
29
+ command = 'BUNDLE_IGNORE_CONFIG=1 bundle install --jobs 4 --path vendor'
30
+ command << %( --without #{@without_groups.join(' ')}) if @without_groups.any?
31
+ command
32
+ end
33
+ end
34
+
35
+ def bundle_gems
36
+ FileUtils.mkdir_p(lib_path)
37
+ Dir.mktmpdir do |tmpdir|
38
+ copy_gem_files(Pathname.new(tmpdir))
39
+ Dir.chdir(tmpdir) do
40
+ Bundler.with_clean_env do
41
+ @shell.exec(bundler_command)
42
+ end
43
+ Dir['vendor/*/*/cache/*'].each do |cached_file|
44
+ FileUtils.rm_rf(cached_file)
45
+ end
46
+ Dir['vendor/ruby/*/extensions/*'].each do |ext_file|
47
+ FileUtils.rm_rf(ext_file)
48
+ end
49
+ @shell.exec('find vendor/ruby/*/gems -name "*.o" -exec rm {} \; 2>&1 || true')
50
+ @shell.exec('find vendor/ruby/*/gems -name "*.so" -exec rm {} \; 2>&1 || true')
51
+ @shell.exec('find vendor/ruby/*/gems -name "*.bundle" -exec rm {} \; 2>&1 || true')
52
+ FileUtils.cp_r('vendor', lib_path, preserve: true)
53
+ end
54
+ end
55
+ end
56
+
57
+ def extract_ruby
58
+ FileUtils.mkdir_p(ruby_path)
59
+ FileUtils.mkdir_p(ruby_vendor_path)
60
+ ruby_archive_path = @fetcher.fetch_ruby
61
+ @shell.exec(%(tar -xzf #{ruby_archive_path} -C #{ruby_path}))
62
+ end
63
+
64
+ def extract_native_gems
65
+ native_gems = find_native_gems
66
+ native_gems.each do |name, version|
67
+ gem_archive_path = @fetcher.fetch_native_gem(name, version)
68
+ @shell.exec %(tar -xzf #{gem_archive_path} -C #{ruby_vendor_path})
69
+ end
70
+ end
71
+
72
+ def find_native_gems
73
+ gemspecs = Dir[ruby_vendor_path.join('*/specifications/*.gemspec')]
74
+ specs = gemspecs.map { |gemspec| Gem::Specification.load(gemspec) }
75
+ with_ext = specs.select { |s| s.extensions.any? }
76
+ with_ext.each_with_object({}) do |spec, hash|
77
+ hash[spec.name] = spec.version.to_s
78
+ end
79
+ end
80
+
81
+ def copy_gem_files(path)
82
+ Dir['Gemfile', 'Gemfile.lock', '*.gemspec'].each do |file|
83
+ if File.exist?(@app_dir.join(file))
84
+ FileUtils.cp(@app_dir.join(file), path.join(File.basename(file)))
85
+ end
86
+ end
87
+ end
88
+
89
+ def create_bundler_config
90
+ copy_gem_files(vendor_path)
91
+ FileUtils.mkdir_p(bundle_path)
92
+ File.open(bundle_path.join('config'), 'w+') do |f|
93
+ f.puts(%(BUNDLE_PATH: .))
94
+ f.puts(%(BUNDLE_WITHOUT: #{@without_groups.join(':')})) if @without_groups.any?
95
+ f.puts(%(BUNDLE_DISABLE_SHARED_GEMS: '1'))
96
+ end
97
+ end
98
+
99
+ def strip_tests
100
+ strip_from_gems(%w[tests test spec])
101
+ end
102
+
103
+ def strip_docs
104
+ strip_from_gems(%w[doc* example* *.txt *.md *.rdoc])
105
+ end
106
+
107
+ def strip_leftovers
108
+ %w[c cpp h rl].each do |ext|
109
+ @shell.exec(%(find #{ruby_vendor_path} -name "*.#{ext}" -exec rm {} \\; 2>&1))
110
+ end
111
+ @shell.exec(%(find #{ruby_vendor_path} -name "extconf.rb" -exec rm {} \\;))
112
+ @shell.exec(%(find #{vendor_gems_glob.join('*', 'ext')} -name "Makefile" -exec rm {} \\; 2>&1))
113
+ @shell.exec(%(find #{vendor_gems_glob.join('*', 'ext')} -name "tmp" -type d 2>&1 | xargs rm -rf))
114
+ end
115
+
116
+ def strip_java_files
117
+ @shell.exec(%(find #{vendor_gems_glob} -name "*.java" -exec rm {} \\;))
118
+ end
119
+
120
+ def strip_git_files
121
+ @shell.exec(%(find #{vendor_gems_glob} -name ".git" -type d | xargs rm -rf))
122
+ @shell.exec(%(find #{bundler_gems_glob} -name ".git" -type d | xargs rm -rf))
123
+ end
124
+
125
+ def strip_from_gems(things)
126
+ things.each do |thing|
127
+ FileUtils.rm_r(Dir[vendor_gems_glob.join('*', thing)])
128
+ FileUtils.rm_r(Dir[bundler_gems_glob.join('*', thing)])
129
+ end
130
+ end
131
+
132
+ def lib_path
133
+ @lib_path ||= Pathname.new(@package_dir).join('lib')
134
+ end
135
+
136
+ def vendor_path
137
+ @vendor_path ||= lib_path.join('vendor')
138
+ end
139
+
140
+ def ruby_vendor_path
141
+ @ruby_vendor_path ||= vendor_path.join('ruby')
142
+ end
143
+
144
+ def bundle_path
145
+ @bundle_path ||= vendor_path.join('.bundle')
146
+ end
147
+
148
+ def ruby_path
149
+ @ruby_path ||= lib_path.join('ruby')
150
+ end
151
+
152
+ def bundler_gems_glob
153
+ @bundler_gems_glob ||= ruby_vendor_path.join('*', 'bundler', 'gems')
154
+ end
155
+
156
+ def vendor_gems_glob
157
+ @vendor_gems_glob ||= ruby_vendor_path.join('*', 'gems')
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ module Tara
4
+ class Shell
5
+ def self.exec(command)
6
+ output = %x(#{command})
7
+ $stderr.puts(%(#{command}: #{output})) if ENV['TARA_DEBUG']
8
+ unless $?.success?
9
+ raise ExecError, %(Command `#{command}` failed with output: #{output})
10
+ end
11
+ output
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module Tara
4
+ VERSION = '0.1.0'
5
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tara
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mathias Söderberg
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-03-20 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Tara packs your Ruby app into a standalone archive with gems and a Ruby
14
+ runtime
15
+ email:
16
+ - mths@sdrbrg.se
17
+ executables:
18
+ - tara
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - bin/tara
23
+ - lib/tara.rb
24
+ - lib/tara/archive.rb
25
+ - lib/tara/archiver.rb
26
+ - lib/tara/cli.rb
27
+ - lib/tara/executable.rb
28
+ - lib/tara/fetcher.rb
29
+ - lib/tara/installer.rb
30
+ - lib/tara/shell.rb
31
+ - lib/tara/version.rb
32
+ homepage: http://github.com/mthssdrbrg/tara
33
+ licenses:
34
+ - BSD-3-Clause
35
+ metadata: {}
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: 2.1.5
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ requirements: []
51
+ rubyforge_project:
52
+ rubygems_version: 2.4.5
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Packs your Ruby app as a standalone archive
56
+ test_files: []
57
+ has_rdoc: