releasy 0.2.0rc1
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.
- data/.gitignore +17 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +20 -0
- data/README.md +165 -0
- data/Rakefile +31 -0
- data/bin/7z.sfx +0 -0
- data/bin/releasy +7 -0
- data/lib/releasy.rb +18 -0
- data/lib/releasy/archivers.rb +12 -0
- data/lib/releasy/archivers/archiver.rb +74 -0
- data/lib/releasy/archivers/dmg.rb +18 -0
- data/lib/releasy/archivers/exe.rb +23 -0
- data/lib/releasy/archivers/seven_zip.rb +12 -0
- data/lib/releasy/archivers/tar_archiver.rb +14 -0
- data/lib/releasy/archivers/tar_bzip2.rb +13 -0
- data/lib/releasy/archivers/tar_gzip.rb +13 -0
- data/lib/releasy/archivers/zip.rb +12 -0
- data/lib/releasy/builders.rb +12 -0
- data/lib/releasy/builders/builder.rb +52 -0
- data/lib/releasy/builders/ocra_builder.rb +43 -0
- data/lib/releasy/builders/osx_app.rb +158 -0
- data/lib/releasy/builders/source.rb +27 -0
- data/lib/releasy/builders/windows_builder.rb +65 -0
- data/lib/releasy/builders/windows_folder.rb +47 -0
- data/lib/releasy/builders/windows_folder_from_ruby_dist.rb +150 -0
- data/lib/releasy/builders/windows_installer.rb +117 -0
- data/lib/releasy/builders/windows_standalone.rb +37 -0
- data/lib/releasy/cli.rb +12 -0
- data/lib/releasy/cli/install_sfx.rb +66 -0
- data/lib/releasy/dsl_wrapper.rb +71 -0
- data/lib/releasy/mixins/exec.rb +14 -0
- data/lib/releasy/mixins/has_archivers.rb +37 -0
- data/lib/releasy/mixins/has_gemspecs.rb +42 -0
- data/lib/releasy/mixins/register.rb +47 -0
- data/lib/releasy/project.rb +300 -0
- data/lib/releasy/version.rb +3 -0
- data/lib/releasy/windows_wrapper_maker.rb +90 -0
- data/lib/releasy/windows_wrapper_maker/Gemfile +8 -0
- data/media/releasy.png +0 -0
- data/releasy.gemspec +37 -0
- data/test/releasy/archivers_test.rb +65 -0
- data/test/releasy/builders/data/Info.plist +51 -0
- data/test/releasy/builders/data/Main.rb +12 -0
- data/test/releasy/builders/data/relapse_runner.rb +1 -0
- data/test/releasy/builders/data/set_app_executable.sh +3 -0
- data/test/releasy/builders/helpers/helper.rb +47 -0
- data/test/releasy/builders/ocra_builder_test.rb +37 -0
- data/test/releasy/builders/osx_app_test.rb +107 -0
- data/test/releasy/builders/source_test.rb +43 -0
- data/test/releasy/builders/windows_builder_test.rb +26 -0
- data/test/releasy/builders/windows_folder_from_ruby_dist_test.rb +105 -0
- data/test/releasy/builders/windows_folder_test.rb +56 -0
- data/test/releasy/builders/windows_installer_test.rb +62 -0
- data/test/releasy/builders/windows_standalone_test.rb +58 -0
- data/test/releasy/cli/install_sfx_test.rb +90 -0
- data/test/releasy/dsl_wrapper_test.rb +79 -0
- data/test/releasy/integration/source_test.rb +122 -0
- data/test/releasy/mixins/register_test.rb +52 -0
- data/test/releasy/project_test.rb +198 -0
- data/test/releasy/windows_wrapper_maker_test.rb +61 -0
- data/test/teststrap.rb +52 -0
- data/test/yard_test.rb +61 -0
- data/test_project/Gemfile +9 -0
- data/test_project/LICENSE.txt +1 -0
- data/test_project/README.txt +2 -0
- data/test_project/bin/test_app +3 -0
- data/test_project/lib/test_app.rb +6 -0
- data/test_project/lib/test_app/stuff.rb +1 -0
- data/test_project/test_app.icns +0 -0
- data/test_project/test_app.ico +0 -0
- data/wrappers/put_wrappers_here_for_testing.txt +17 -0
- metadata +236 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
module Releasy
|
2
|
+
module Mixins
|
3
|
+
# Adds an {#exec} method that prints out if the project is verbose.
|
4
|
+
module Exec
|
5
|
+
protected
|
6
|
+
def exec(command)
|
7
|
+
puts command if project.verbose?
|
8
|
+
result = %x[#{command}]
|
9
|
+
puts result if project.verbose?
|
10
|
+
result
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Releasy
|
2
|
+
module Mixins
|
3
|
+
# An object that owns one or more instances of {Archivers::Archiver}
|
4
|
+
module HasArchivers
|
5
|
+
# Add an archive type to be generated for each of your outputs.
|
6
|
+
# @see Project#initialize
|
7
|
+
# @param type [:exe, :"7z", :tar_bz2, :tar_gz, :zip]
|
8
|
+
# @return [Project] self
|
9
|
+
def add_archive(type, &block)
|
10
|
+
raise ArgumentError, "Unsupported archive format #{type.inspect}" unless Archivers.has_type? type
|
11
|
+
raise ConfigError, "Already have archive format #{type.inspect}" if archivers.any? {|a| a.type == type }
|
12
|
+
|
13
|
+
archiver = Archivers[type].new(respond_to?(:project) ? project : self)
|
14
|
+
archivers << archiver
|
15
|
+
|
16
|
+
if block_given?
|
17
|
+
if block.arity == 0
|
18
|
+
DSLWrapper.new(archiver, &block)
|
19
|
+
else
|
20
|
+
yield archiver
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
archiver
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
def archivers; @archivers ||= []; end
|
29
|
+
|
30
|
+
protected
|
31
|
+
# @return [Array<Archiver>]
|
32
|
+
def active_archivers
|
33
|
+
archivers
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Releasy
|
2
|
+
module Mixins
|
3
|
+
# An object that manages a list of {#gemspecs}
|
4
|
+
module HasGemspecs
|
5
|
+
# @return [Array<Gem>] List of gemspecs used by the application. Will default to the gems in the `default` Bundler group or, if Bundler isn't used, all gems loaded by rubygems.
|
6
|
+
attr_accessor :gemspecs
|
7
|
+
|
8
|
+
protected
|
9
|
+
def setup
|
10
|
+
@gemspecs = if defined? Bundler
|
11
|
+
Bundler.definition.specs_for([:default]).to_a
|
12
|
+
else
|
13
|
+
Gem.loaded_specs.values
|
14
|
+
end
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
# Don't include binary gems already in the .app or bundler, since it will get confused.
|
20
|
+
def vendored_gem_names(ignored_gems); (gemspecs.map(&:name) - ignored_gems).sort; end
|
21
|
+
|
22
|
+
protected
|
23
|
+
def copy_gems(gems, destination)
|
24
|
+
puts "Copying source gems from system" if project.verbose?
|
25
|
+
|
26
|
+
gems_dir = "#{destination}/gems"
|
27
|
+
specs_dir = "#{destination}/specifications"
|
28
|
+
mkdir_p gems_dir
|
29
|
+
mkdir_p specs_dir
|
30
|
+
|
31
|
+
gems.each do |gem|
|
32
|
+
spec = gemspecs.find {|g| g.name == gem }
|
33
|
+
gem_dir = spec.full_gem_path
|
34
|
+
puts "Copying gem: #{spec.name} #{spec.version}" if project.verbose?
|
35
|
+
cp_r gem_dir, gems_dir
|
36
|
+
spec_file = File.expand_path("../../specifications/#{File.basename gem_dir}.gemspec", gem_dir)
|
37
|
+
cp_r spec_file, specs_dir
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Releasy
|
4
|
+
module Mixins
|
5
|
+
# Maintains a registry of classes within a module.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# module Frogs
|
9
|
+
# extend Releasy::Mixins::Register
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# class Frogs::BlueFrog
|
13
|
+
# TYPE = :blue # Type must be defined before registering.
|
14
|
+
# Frogs.register self
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# class Frogs::RedFrog
|
18
|
+
# TYPE = :red # Type must be defined before registering.
|
19
|
+
# Frogs.register self
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# Frogs[:blue] #=> Frogs::BlueFrog
|
23
|
+
# Frogs.has_type? :blue #=> true
|
24
|
+
# Frogs.types #=> [:blue, :red]
|
25
|
+
# Frogs.values #=> [Frogs::BlueFrog, Frogs::RedFrog]
|
26
|
+
module Register
|
27
|
+
include Enumerable
|
28
|
+
extend Forwardable
|
29
|
+
|
30
|
+
def_delegators :registered, :[], :each, :values
|
31
|
+
def_delegator :registered, :has_key?, :has_type?
|
32
|
+
def_delegator :registered, :keys, :types
|
33
|
+
|
34
|
+
# Register a class with this register of classes of that type.
|
35
|
+
# @param klass [Object] Object, which is defined within the namespace being registered with.
|
36
|
+
def register(klass)
|
37
|
+
raise TypeError, "Can only register classes" unless klass.is_a? Class
|
38
|
+
raise ArgumentError, "Can't register a class not within this module" unless klass.name.split('::')[0...-1].join('::') == name
|
39
|
+
raise ArgumentError, "To register, a class must have TYPE defined" unless klass.const_defined? :TYPE
|
40
|
+
registered[klass::TYPE] = klass
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
def registered; @registered ||= {}; end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,300 @@
|
|
1
|
+
require 'releasy/dsl_wrapper'
|
2
|
+
require 'releasy/builders'
|
3
|
+
require 'releasy/archivers'
|
4
|
+
require "releasy/mixins/has_archivers"
|
5
|
+
|
6
|
+
module Releasy
|
7
|
+
# A description of the Ruby application that is being build for release and what packages to make from it.
|
8
|
+
#
|
9
|
+
# @attr underscored_name [String] Project name underscored (as used in file names), which will be derived from {#name}, but can be manually set.
|
10
|
+
# @attr underscored_version [String] Version number, underscored so it can be used in file names, which will be derived from {#version}, but can be manually set.
|
11
|
+
# @attr executable [String] Name of executable to run (defaults to 'bin/<underscored_name>')
|
12
|
+
# @attr_reader folder_base [String] The path to the folders to create. All variations of output will be based on extending this path.
|
13
|
+
# @attr files [Rake::FileList] List of files to include in package.
|
14
|
+
# @attr exposed_files [Rake::FileList] Files which should always be copied into the archive folder root, so they are always visible to the user. e.g readme, change-log and/or license files.
|
15
|
+
class Project
|
16
|
+
include Rake::DSL
|
17
|
+
include Mixins::HasArchivers
|
18
|
+
|
19
|
+
DEFAULT_PACKAGE_FOLDER = "pkg"
|
20
|
+
|
21
|
+
attr_writer :underscored_name, :underscored_version, :executable
|
22
|
+
|
23
|
+
# @return [String] Name of the application, such as "My Application".
|
24
|
+
attr_accessor :name
|
25
|
+
# @return [String] Version number as a string (for example, "1.2.0").
|
26
|
+
attr_accessor :version
|
27
|
+
# @return [String] Folder to output to (defaults to 'pkg/')
|
28
|
+
attr_accessor :output_path
|
29
|
+
|
30
|
+
# Make the tasks give more detailed output.
|
31
|
+
# @return [nil]
|
32
|
+
def verbose; @verbose = true; nil; end
|
33
|
+
# Make the tasks give less detailed output.
|
34
|
+
# @return [nil]
|
35
|
+
def quiet; @verbose = false; nil; end
|
36
|
+
def verbose?; @verbose; end
|
37
|
+
|
38
|
+
# Create MD5 hashes for created archives.
|
39
|
+
# @return [nil]
|
40
|
+
def create_md5s; @create_md5s = true; nil; end
|
41
|
+
def create_md5s?; @create_md5s; end
|
42
|
+
protected :create_md5s?
|
43
|
+
|
44
|
+
# Verbosity of the console output.
|
45
|
+
# @return [Boolean] True to make the tasks output more information.
|
46
|
+
def verbose?; @verbose; end
|
47
|
+
|
48
|
+
def exposed_files; @exposed_files; end
|
49
|
+
def exposed_files=(files); @exposed_files = Rake::FileList.new files; end
|
50
|
+
|
51
|
+
def files; @files; end
|
52
|
+
def files=(files); @files = Rake::FileList.new files; end
|
53
|
+
|
54
|
+
# @return [String]
|
55
|
+
def to_s; "<#{self.class}#{name ? " #{name}" : ""}#{version ? " #{version}" : ""}>"; end
|
56
|
+
|
57
|
+
def underscored_name
|
58
|
+
if @underscored_name or @name.nil?
|
59
|
+
@underscored_name
|
60
|
+
else
|
61
|
+
@name.strip.downcase.gsub(/[^a-z0-9_\- ]/i, '').split(/[\-_ ]+/).join("_")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def underscored_version
|
66
|
+
if @underscored_version or @version.nil?
|
67
|
+
@underscored_version
|
68
|
+
else
|
69
|
+
@version.gsub(".", "_")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def executable
|
74
|
+
if @executable or underscored_name.nil?
|
75
|
+
@executable
|
76
|
+
else
|
77
|
+
"bin/#{underscored_name}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Can be used with or without a block to generate building and packaging tasks.
|
82
|
+
#
|
83
|
+
# @overload initialize(&block)
|
84
|
+
# Using a block, the API is more terse and the tasks are automatically generated
|
85
|
+
# when the block is closed (Uses a {DSLWrapper}). This is the preferred syntax!
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# Releasy::Project.new do
|
89
|
+
# name "My Application"
|
90
|
+
# version "1.2.4"
|
91
|
+
# add_build :source do
|
92
|
+
# add_archive :tar_gz do
|
93
|
+
# extension ".tgz"
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# @yield [] Block is evaluated in context of a {DSLWrapper} wrapping self.
|
99
|
+
#
|
100
|
+
# @overload initialize(&block)
|
101
|
+
# Using a block that takes a parameter, self is passed, and so the API is similar to a Gem::Specification.
|
102
|
+
# The tasks are automatically generated when the block is closed
|
103
|
+
#
|
104
|
+
# @example
|
105
|
+
# Releasy::Project.new do |p|
|
106
|
+
# p.name = "My Application"
|
107
|
+
# p.version = "1.2.4"
|
108
|
+
# p.add_build :source do |b|
|
109
|
+
# b.add_archive :tar_gz do |a|
|
110
|
+
# a.extension = ".tgz"
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# @yieldparam project [Project] new project
|
116
|
+
#
|
117
|
+
# @overload initialize
|
118
|
+
# Without using blocks, the {Project} can be accessed directly. It is recommended that a block is used.
|
119
|
+
#
|
120
|
+
# @example
|
121
|
+
# project = Releasy::Project.new
|
122
|
+
# project.name = "My Application"
|
123
|
+
# project.version = "1.2.4"
|
124
|
+
# builder = project.add_build :source
|
125
|
+
# archiver = builder.add_archive :zip
|
126
|
+
# archiver.extension = ".tgz"
|
127
|
+
# project.generate_tasks # This has to be done manually.
|
128
|
+
#
|
129
|
+
def initialize(&block)
|
130
|
+
super()
|
131
|
+
|
132
|
+
@builders = []
|
133
|
+
@links = {}
|
134
|
+
@files = Rake::FileList.new
|
135
|
+
@exposed_files = Rake::FileList.new
|
136
|
+
@output_path = DEFAULT_PACKAGE_FOLDER
|
137
|
+
@verbose = true
|
138
|
+
@create_md5s = false
|
139
|
+
@name = @underscored_name = @underscored_version = nil
|
140
|
+
@version = @executable = nil
|
141
|
+
|
142
|
+
setup
|
143
|
+
|
144
|
+
if block_given?
|
145
|
+
if block.arity == 0
|
146
|
+
DSLWrapper.new(self, &block)
|
147
|
+
else
|
148
|
+
yield self
|
149
|
+
end
|
150
|
+
|
151
|
+
generate_tasks
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Add a type of output to produce. Must define at least one of these.
|
156
|
+
# @see #initialize
|
157
|
+
# @param type [:osx_app, :source, :windows_folder, :windows_folder_from_ruby_dist, :windows_installer, :windows_standalone]
|
158
|
+
# @return [Project] self
|
159
|
+
def add_build(type, &block)
|
160
|
+
raise ArgumentError, "Unsupported output type #{type}" unless Builders.has_type? type
|
161
|
+
raise ConfigError, "Already have output #{type.inspect}" if @builders.any? {|b| b.type == type }
|
162
|
+
|
163
|
+
builder = Builders[type].new(self)
|
164
|
+
@builders << builder
|
165
|
+
|
166
|
+
if block_given?
|
167
|
+
if block.arity == 0
|
168
|
+
DSLWrapper.new(builder, &block)
|
169
|
+
else
|
170
|
+
yield builder
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
builder
|
175
|
+
end
|
176
|
+
|
177
|
+
# Add a link file to be included in the win32 releases. Will create the file _title.url_ for you.
|
178
|
+
#
|
179
|
+
# @param url [String] Url to link to.
|
180
|
+
# @param title [String] Name of file to create.
|
181
|
+
# @return [Project] self
|
182
|
+
def add_link(url, title)
|
183
|
+
@links[url] = title
|
184
|
+
|
185
|
+
self
|
186
|
+
end
|
187
|
+
|
188
|
+
# Generates all tasks required by the user. Automatically called at the end of the block, if {#initialize} is given a block.
|
189
|
+
# @return [Project] self
|
190
|
+
def generate_tasks
|
191
|
+
raise ConfigError, "Must specify at least one valid output for this OS with #add_build before tasks can be generated" if @builders.empty?
|
192
|
+
|
193
|
+
# Even if there are builders specified, none may work on this platform.
|
194
|
+
return if active_builders.empty?
|
195
|
+
|
196
|
+
build_outputs = []
|
197
|
+
build_groups = Hash.new {|h, k| h[k] = [] }
|
198
|
+
|
199
|
+
active_builders.each do |builder|
|
200
|
+
builder.send :generate_tasks
|
201
|
+
task_name = "build:#{builder.type.to_s.tr("_", ":")}"
|
202
|
+
|
203
|
+
if builder.type.to_s =~ /_/
|
204
|
+
task_group = builder.send :task_group
|
205
|
+
build_groups[task_group] << task_name
|
206
|
+
build_outputs << "build:#{task_group}"
|
207
|
+
else
|
208
|
+
build_outputs << task_name
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
build_groups.each_pair do |group, tasks|
|
213
|
+
desc "Build all #{group} outputs"
|
214
|
+
task "build:#{group}" => tasks
|
215
|
+
end
|
216
|
+
|
217
|
+
desc "Build all outputs"
|
218
|
+
task "build" => build_outputs
|
219
|
+
|
220
|
+
generate_archive_tasks
|
221
|
+
|
222
|
+
self
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
def folder_base
|
227
|
+
File.join(output_path, "#{underscored_name}#{version ? "_#{underscored_version}" : ""}")
|
228
|
+
end
|
229
|
+
|
230
|
+
protected
|
231
|
+
# Only allow access to this from Builder
|
232
|
+
# @return [Hash]
|
233
|
+
def links; @links; end
|
234
|
+
|
235
|
+
protected
|
236
|
+
def setup; end
|
237
|
+
|
238
|
+
protected
|
239
|
+
# @return [Array<Builder>]
|
240
|
+
def active_builders
|
241
|
+
@builders.find_all(&:valid_for_platform?)
|
242
|
+
end
|
243
|
+
|
244
|
+
protected
|
245
|
+
# Generates the general tasks for compressing folders.
|
246
|
+
def generate_archive_tasks
|
247
|
+
return if active_builders.empty?
|
248
|
+
|
249
|
+
windows_tasks = []
|
250
|
+
osx_tasks = []
|
251
|
+
top_level_tasks = []
|
252
|
+
active_builders.each do |builder|
|
253
|
+
output_task = builder.type.to_s.sub '_', ':'
|
254
|
+
|
255
|
+
archivers = active_archivers(builder)
|
256
|
+
archivers.each do |archiver|
|
257
|
+
archiver.send :generate_tasks, output_task, builder.send(:folder)
|
258
|
+
end
|
259
|
+
|
260
|
+
desc "Package all #{builder.type}"
|
261
|
+
task "package:#{output_task}" => archivers.map {|c| "package:#{output_task}:#{c.type}" }
|
262
|
+
|
263
|
+
case output_task
|
264
|
+
when /^windows:/
|
265
|
+
windows_tasks << "package:#{output_task}"
|
266
|
+
top_level_tasks << "package:windows" unless top_level_tasks.include? "package:windows"
|
267
|
+
when /^osx:/
|
268
|
+
osx_tasks << "package:#{output_task}"
|
269
|
+
top_level_tasks << "package:osx" unless top_level_tasks.include? "package:osx"
|
270
|
+
else
|
271
|
+
top_level_tasks << "package:#{output_task}"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
unless windows_tasks.empty?
|
276
|
+
desc "Package all Windows"
|
277
|
+
task "package:windows" => windows_tasks
|
278
|
+
end
|
279
|
+
|
280
|
+
unless osx_tasks.empty?
|
281
|
+
desc "Package all OS X"
|
282
|
+
task "package:osx" => osx_tasks
|
283
|
+
end
|
284
|
+
|
285
|
+
desc "Package all"
|
286
|
+
task "package" => top_level_tasks
|
287
|
+
|
288
|
+
self
|
289
|
+
end
|
290
|
+
|
291
|
+
protected
|
292
|
+
def active_archivers(builder)
|
293
|
+
# Use archivers specifically set on the builder and those set globally that aren't on the builder.
|
294
|
+
archivers = builder.send(:active_archivers)
|
295
|
+
archiver_types = archivers.map(&:type)
|
296
|
+
|
297
|
+
archivers + super().reject {|a| archiver_types.include? a.type }
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|