choctop 0.9.6 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -0
- data/Manifest.txt +16 -7
- data/README.rdoc +7 -3
- data/app_generators/install_choctop/templates/Rakefile.erb +2 -3
- data/features/development.feature +5 -5
- data/features/dmg.feature +34 -28
- data/features/fixtures/SampleApp/README.txt +1 -0
- data/features/fixtures/SampleApp/build/Release/README.txt +1 -0
- data/features/fixtures/SampleApp/build/Release/dmg/README.txt +1 -0
- data/features/initial_generator.feature +10 -10
- data/features/rake_tasks.feature +15 -15
- data/features/sparkle_feed.feature +25 -25
- data/features/step_definitions/common_steps.rb +177 -0
- data/features/{steps → step_definitions}/dmg_steps.rb +12 -4
- data/features/{steps → step_definitions}/file_attribute_steps.rb +2 -2
- data/features/{steps/generator.rb → step_definitions/generator_steps.rb} +2 -2
- data/features/{steps/remote.rb → step_definitions/remote_steps.rb} +2 -2
- data/features/{steps → step_definitions}/xcode_steps.rb +1 -1
- data/features/support/common.rb +44 -0
- data/features/{steps → support}/env.rb +0 -0
- data/features/support/matchers.rb +11 -0
- data/lib/choctop.rb +66 -28
- data/lib/choctop/appcast.rb +1 -1
- data/lib/choctop/dmg.rb +22 -3
- data/spec/choctop_spec.rb +85 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +5 -0
- data/tasks/rspec.rake +21 -0
- metadata +44 -13
- data/features/steps/common.rb +0 -248
@@ -8,7 +8,15 @@ Given /is configured for custom Applications icon$/ do
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
Given /^is configured for an asset file "([^\"]*)" to be included in dmg$/ do |file|
|
12
|
+
in_project_folder do
|
13
|
+
append_to_file "Rakefile", <<-RUBY.gsub(/^ /, '')
|
14
|
+
$sparkle.add_file "#{file}", :position=> [347, 65]
|
15
|
+
RUBY
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
When /^dmg "(.*)" is mounted as "(.*)"$/ do |dmg, name|
|
12
20
|
@stdout = File.expand_path(File.join(@tmp_root, "hdiutil.out"))
|
13
21
|
in_project_folder do
|
14
22
|
@mountpoint = ChocTop.new.mountpoint
|
@@ -22,19 +30,19 @@ def in_mounted_volume(&block)
|
|
22
30
|
FileUtils.chdir(@volume_path, &block)
|
23
31
|
end
|
24
32
|
|
25
|
-
Then %r{^folder
|
33
|
+
Then %r{^folder "(.*)" in mounted volume (is|is not) created} do |folder, is|
|
26
34
|
in_mounted_volume do
|
27
35
|
File.exists?(folder).should(is == 'is' ? be_true : be_false)
|
28
36
|
end
|
29
37
|
end
|
30
38
|
|
31
|
-
Then %r{^file
|
39
|
+
Then %r{^file "(.*)" in mounted volume (is|is not) created} do |file, is|
|
32
40
|
in_mounted_volume do
|
33
41
|
File.exists?(file).should(is == 'is' ? be_true : be_false)
|
34
42
|
end
|
35
43
|
end
|
36
44
|
|
37
|
-
Then /^file
|
45
|
+
Then /^file "(.*)" in mounted volume (is|is not) invisible$/ do |file, is|
|
38
46
|
in_mounted_volume do
|
39
47
|
`GetFileInfo -aV '#{@volume_path}/#{file}'`.to_i.should_not == (is == 'is' ? 0 : 1)
|
40
48
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
Then /^file
|
1
|
+
Then /^file "(.*)" in mounted volume has GetFileInfo (.*) "(.*)"/ do |file, file_info_type, value|
|
2
2
|
flags = case file_info_type.to_sym
|
3
3
|
when :type; "-t"
|
4
4
|
when :alias; "-aa"
|
@@ -9,7 +9,7 @@ Then /^file '(.*)' in mounted volume has GetFileInfo (.*) '(.*)'/ do |file, file
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
Then /^file
|
12
|
+
Then /^file "(.*)" in mounted volume is aliased to "(.*)"/ do |file, target|
|
13
13
|
in_mounted_volume do
|
14
14
|
puts "TODO - how to get applescript to test this?"
|
15
15
|
end
|
@@ -24,7 +24,7 @@ end
|
|
24
24
|
Given /Rakefile constants rewired for local rsync/ do
|
25
25
|
end
|
26
26
|
|
27
|
-
Given /^a Cocoa app with choctop installed called
|
27
|
+
Given /^a Cocoa app with choctop installed called "(.*)"$/ do |name|
|
28
28
|
Given "a safe folder"
|
29
29
|
@remote_folder = File.expand_path(File.join(@tmp_root, 'website'))
|
30
30
|
FileUtils.rm_rf @remote_folder
|
@@ -33,7 +33,7 @@ Given /^a Cocoa app with choctop installed called '(.*)'$/ do |name|
|
|
33
33
|
`cp -r '#{app_path}' #{@tmp_root}/ 2> /dev/null`
|
34
34
|
`rm -rf '#{@tmp_root}/#{name}/build'`
|
35
35
|
setup_active_project_folder name
|
36
|
-
Given
|
36
|
+
Given %Q{I run local executable "install_choctop" with arguments "."}
|
37
37
|
Given "Rakefile wired to use development code instead of installed RubyGem"
|
38
38
|
Given "Rakefile constants rewired for local rsync"
|
39
39
|
ENV['NO_FINDER'] = 'YES' # disable Finder during tests
|
@@ -1,10 +1,10 @@
|
|
1
|
-
Then %r{^remote folder
|
1
|
+
Then %r{^remote folder "(.*)" is created} do |folder|
|
2
2
|
FileUtils.chdir @remote_folder do
|
3
3
|
File.exists?(folder).should be_true
|
4
4
|
end
|
5
5
|
end
|
6
6
|
|
7
|
-
Then %r{^remote file
|
7
|
+
Then %r{^remote file "(.*)" (is|is not) created} do |file, is|
|
8
8
|
FileUtils.chdir @remote_folder do
|
9
9
|
File.exists?(file).should(is == 'is' ? be_true : be_false)
|
10
10
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module CommonHelpers
|
2
|
+
def in_tmp_folder(&block)
|
3
|
+
FileUtils.chdir(@tmp_root, &block)
|
4
|
+
end
|
5
|
+
|
6
|
+
def in_project_folder(&block)
|
7
|
+
project_folder = @active_project_folder || @tmp_root
|
8
|
+
FileUtils.chdir(project_folder, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def in_home_folder(&block)
|
12
|
+
FileUtils.chdir(@home_path, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def force_local_lib_override(project_name = @project_name)
|
16
|
+
rakefile = File.read(File.join(project_name, 'Rakefile'))
|
17
|
+
File.open(File.join(project_name, 'Rakefile'), "w+") do |f|
|
18
|
+
f << "$:.unshift('#{@lib_path}')\n"
|
19
|
+
f << rakefile
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup_active_project_folder project_name
|
24
|
+
@active_project_folder = File.join(@tmp_root, project_name)
|
25
|
+
FileUtils.mkdir_p(@active_project_folder)
|
26
|
+
@project_name = project_name
|
27
|
+
end
|
28
|
+
|
29
|
+
def prepend_to_file(filename, text)
|
30
|
+
file = File.read(filename)
|
31
|
+
File.open(filename, "w+") do |f|
|
32
|
+
f << text + "\n"
|
33
|
+
f << file
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def append_to_file(filename, text)
|
38
|
+
File.open(filename, "a") do |f|
|
39
|
+
f << text + "\n"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
World(CommonHelpers)
|
File without changes
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Matchers
|
2
|
+
def contain(expected)
|
3
|
+
simple_matcher("contain #{expected.inspect}") do |given, matcher|
|
4
|
+
matcher.failure_message = "expected #{given.inspect} to contain #{expected.inspect}"
|
5
|
+
matcher.negative_failure_message = "expected #{given.inspect} not to contain #{expected.inspect}"
|
6
|
+
given.index expected
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
World(Matchers)
|
data/lib/choctop.rb
CHANGED
@@ -5,12 +5,17 @@ require "fileutils"
|
|
5
5
|
require "yaml"
|
6
6
|
require "builder"
|
7
7
|
require "erb"
|
8
|
+
require "uri"
|
8
9
|
require "osx/cocoa"
|
9
10
|
require "active_support"
|
10
11
|
require "RedCloth"
|
11
12
|
|
12
13
|
class ChocTop
|
13
|
-
VERSION = '0.
|
14
|
+
VERSION = '0.10.0'
|
15
|
+
|
16
|
+
# Path to the Info.plist
|
17
|
+
# Default: "Info.plist"
|
18
|
+
attr_accessor :info_plist_path
|
14
19
|
|
15
20
|
# The name of the Cocoa application
|
16
21
|
# Default: info_plist['CFBundleExecutable'] or project folder name if "${EXECUTABLE_NAME}"
|
@@ -23,20 +28,29 @@ class ChocTop
|
|
23
28
|
# The target name of the distributed DMG file
|
24
29
|
# Default: #{name}.app
|
25
30
|
attr_accessor :target
|
31
|
+
|
32
|
+
# The build type of the distributed DMG file
|
33
|
+
# Default: Release
|
34
|
+
attr_accessor :build_type
|
35
|
+
|
36
|
+
# The Sparkle feed URL
|
37
|
+
# Default: info_plist['SUFeedURL']
|
38
|
+
attr_accessor :su_feed_url
|
26
39
|
|
27
40
|
# The host name, e.g. some-domain.com
|
41
|
+
# Default: host from base_url
|
28
42
|
attr_accessor :host
|
29
43
|
|
30
44
|
# The url from where the xml + dmg files will be downloaded
|
31
|
-
# Default:
|
32
|
-
|
33
|
-
def base_url
|
34
|
-
@base_url ||= "http://#{host}"
|
35
|
-
end
|
45
|
+
# Default: dir path from appcast_filename
|
46
|
+
attr_accessor :base_url
|
36
47
|
|
37
48
|
# The file name for generated release notes for the latest release
|
38
49
|
# Default: release_notes.html
|
39
50
|
attr_accessor :release_notes
|
51
|
+
|
52
|
+
# List of files/bundles to be packaged into the DMG
|
53
|
+
attr_accessor :files
|
40
54
|
|
41
55
|
# The path for an HTML template into which the release_notes.txt are inserted
|
42
56
|
# after conversion to HTML
|
@@ -60,6 +74,13 @@ class ChocTop
|
|
60
74
|
# Default: -aCv
|
61
75
|
attr_accessor :rsync_args
|
62
76
|
|
77
|
+
# Folder from where all files will be copied into the DMG
|
78
|
+
# Files are copied here if specified with +add_file+ before DMG creation
|
79
|
+
attr_accessor :src_folder
|
80
|
+
def src_folder
|
81
|
+
@src_folder ||= "build/#{build_type}/dmg"
|
82
|
+
end
|
83
|
+
|
63
84
|
# Generated filename for a distribution, from name, version and .dmg
|
64
85
|
# e.g. MyApp-1.0.0.dmg
|
65
86
|
def pkg_name
|
@@ -136,41 +157,61 @@ class ChocTop
|
|
136
157
|
end
|
137
158
|
|
138
159
|
def info_plist
|
139
|
-
@info_plist ||= OSX::NSDictionary.dictionaryWithContentsOfFile(File.expand_path(
|
160
|
+
@info_plist ||= OSX::NSDictionary.dictionaryWithContentsOfFile(File.expand_path(info_plist_path)) || {}
|
161
|
+
end
|
162
|
+
|
163
|
+
# Add an explicit file/bundle/folder into the DMG
|
164
|
+
# Required option:
|
165
|
+
# +:position+ - two item array [x, y] window position
|
166
|
+
def add_file(path, options)
|
167
|
+
throw "add_files #{path}, :position => [x,y] option is missing" unless options[:position]
|
168
|
+
self.files ||= {}
|
169
|
+
files[path] = options
|
140
170
|
end
|
141
171
|
|
142
172
|
def initialize
|
143
173
|
$sparkle = self # define a global variable for this object
|
144
174
|
|
175
|
+
yield self if block_given?
|
176
|
+
|
145
177
|
# Defaults
|
146
|
-
@
|
147
|
-
@name
|
148
|
-
@
|
149
|
-
@
|
150
|
-
@
|
151
|
-
@
|
152
|
-
@release_notes_template = "release_notes_template.html.erb"
|
153
|
-
@rsync_args = '-aCv --progress'
|
178
|
+
@info_plist_path ||= 'Info.plist'
|
179
|
+
@name ||= info_plist['CFBundleExecutable']
|
180
|
+
@name = File.basename(File.expand_path(".")) if name.to_s == "${EXECUTABLE_NAME}"
|
181
|
+
@version ||= info_plist['CFBundleVersion']
|
182
|
+
@target ||= "#{name}.app"
|
183
|
+
@build_type = ENV['BUILD_TYPE'] || 'Release'
|
154
184
|
|
155
|
-
@
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
@
|
160
|
-
|
185
|
+
if @su_feed_url = info_plist['SUFeedURL']
|
186
|
+
@appcast_filename ||= File.basename(su_feed_url)
|
187
|
+
@base_url ||= File.dirname(su_feed_url)
|
188
|
+
end
|
189
|
+
if @base_url
|
190
|
+
@host ||= URI.parse(base_url).host
|
191
|
+
end
|
192
|
+
@release_notes ||= 'release_notes.html'
|
193
|
+
@release_notes_template ||= "release_notes_template.html.erb"
|
194
|
+
@rsync_args ||= '-aCv --progress'
|
161
195
|
|
162
|
-
|
196
|
+
@background_file ||= File.dirname(__FILE__) + "/../assets/sky_background.jpg"
|
197
|
+
@app_icon_position ||= [175, 65]
|
198
|
+
@applications_icon_position ||= [347, 270]
|
199
|
+
@volume_icon ||= File.dirname(__FILE__) + "/../assets/DefaultVolumeIcon.icns"
|
200
|
+
@icon_size ||= 104
|
201
|
+
@icon_text_size ||= 12
|
163
202
|
|
203
|
+
add_file "build/#{build_type}/#{target}", :position => app_icon_position
|
204
|
+
|
164
205
|
define_tasks
|
165
206
|
end
|
166
207
|
|
167
208
|
def define_tasks
|
168
209
|
return unless Object.const_defined?("Rake")
|
169
210
|
|
170
|
-
desc "Build Xcode
|
171
|
-
task :build => "build
|
211
|
+
desc "Build Xcode #{build_type}"
|
212
|
+
task :build => "build/#{build_type}/#{target}/Contents/Info.plist"
|
172
213
|
|
173
|
-
task "build
|
214
|
+
task "build/#{build_type}/#{target}/Contents/Info.plist" do
|
174
215
|
make_build
|
175
216
|
end
|
176
217
|
|
@@ -195,9 +236,6 @@ class ChocTop
|
|
195
236
|
upload_appcast
|
196
237
|
end
|
197
238
|
|
198
|
-
desc "Create dmg, update appcast file, and upload to host"
|
199
|
-
task :appcast => %w[force_build dmg force_feed upload]
|
200
|
-
|
201
239
|
task :detach_dmg do
|
202
240
|
detach_dmg
|
203
241
|
end
|
data/lib/choctop/appcast.rb
CHANGED
data/lib/choctop/dmg.rb
CHANGED
@@ -1,9 +1,18 @@
|
|
1
1
|
module ChocTop::Dmg
|
2
|
+
def prepare_files
|
3
|
+
FileUtils.mkdir_p(src_folder)
|
4
|
+
files.each do |file|
|
5
|
+
path, options = file
|
6
|
+
FileUtils.cp_r(path, src_folder)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
2
10
|
def make_dmg
|
11
|
+
prepare_files
|
3
12
|
FileUtils.rm_rf build_path
|
4
13
|
FileUtils.mkdir_p build_path
|
5
14
|
FileUtils.mkdir_p mountpoint # TODO can we remove random mountpoints?
|
6
|
-
sh "hdiutil create -format UDRW -quiet -volname '#{name}' -srcfolder '
|
15
|
+
sh "hdiutil create -format UDRW -quiet -volname '#{name}' -srcfolder '#{src_folder}' '#{pkg}'"
|
7
16
|
sh "hdiutil attach '#{pkg}' -mountpoint '#{volume_path}' -noautoopen -quiet"
|
8
17
|
sh "bless --folder '#{volume_path}' --openfolder '#{volume_path}'"
|
9
18
|
sh "sleep 1"
|
@@ -47,7 +56,7 @@ module ChocTop::Dmg
|
|
47
56
|
FileUtils.mkdir_p(File.dirname(target_background))
|
48
57
|
FileUtils.cp(background_file, target_background)
|
49
58
|
end
|
50
|
-
|
59
|
+
script = <<-SCRIPT.gsub(/^ /, '')
|
51
60
|
tell application "Finder"
|
52
61
|
set mountpoint to POSIX file ("#{volume_path}" as string) as alias
|
53
62
|
tell folder mountpoint
|
@@ -63,7 +72,7 @@ module ChocTop::Dmg
|
|
63
72
|
set icon size of the icon view options of container window to #{icon_size}
|
64
73
|
set text size of the icon view options of container window to #{icon_text_size}
|
65
74
|
set arrangement of the icon view options of container window to not arranged
|
66
|
-
|
75
|
+
#{set_position_of_files}
|
67
76
|
set position of item "Applications" to {#{applications_icon_position.join(", ")}}
|
68
77
|
set the bounds of the container window to {#{window_bounds.join(", ")}}
|
69
78
|
set background picture of the icon view options of container window to file "#{volume_background.gsub(/\//,':')}"
|
@@ -75,9 +84,19 @@ module ChocTop::Dmg
|
|
75
84
|
delay 5
|
76
85
|
end tell
|
77
86
|
SCRIPT
|
87
|
+
run_applescript(script)
|
78
88
|
sh "SetFile -a V '#{target_background}'" if background_file
|
79
89
|
end
|
80
90
|
|
91
|
+
def set_position_of_files
|
92
|
+
files.map do |file_options|
|
93
|
+
path, options = file_options
|
94
|
+
target = File.basename(path)
|
95
|
+
position = options[:position].join(", ")
|
96
|
+
%Q{set position of item "#{target}" to {#{position}}}
|
97
|
+
end.join("\n")
|
98
|
+
end
|
99
|
+
|
81
100
|
def configure_applications_icon
|
82
101
|
run_applescript <<-SCRIPT.gsub(/^ /, ''), "apps_icon_script"
|
83
102
|
tell application "Finder"
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
# Time to add your specs!
|
4
|
+
# http://rspec.info/
|
5
|
+
describe ChocTop do
|
6
|
+
attr_reader :choctop
|
7
|
+
|
8
|
+
describe "default" do
|
9
|
+
before(:each) do
|
10
|
+
FileUtils.chdir(File.dirname(__FILE__) + "/../features/fixtures/SampleApp") do
|
11
|
+
@choctop = ChocTop.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should get name from Info.plist['CFBundleExecutable']" do
|
16
|
+
choctop.name.should == 'SampleApp'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should get version from Info.plist['CFBundleVersion']" do
|
20
|
+
choctop.version.should == '0.1.0'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should derive host from Info.plist['SUFeedURL']" do
|
24
|
+
choctop.host.should == 'mocra.com'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should derive base_url from Info.plist['SUFeedURL']" do
|
28
|
+
choctop.base_url.should == 'http://mocra.com/sample_app'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should derive appcast_filename from Info.plist['SUFeedURL']" do
|
32
|
+
choctop.appcast_filename.should == 'my_feed.xml'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "add_files" do
|
37
|
+
before(:each) do
|
38
|
+
FileUtils.chdir(File.dirname(__FILE__) + "/../features/fixtures/SampleApp") do
|
39
|
+
@choctop = ChocTop.new
|
40
|
+
@choctop.add_file "README.txt", :position => [50, 100]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should have build/Release/SampleApp.app as a file/bundle" do
|
45
|
+
@choctop.files.keys.should be_include('build/Release/SampleApp.app')
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should have README.txt as a file" do
|
49
|
+
@choctop.files.keys.include?('README.txt')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should have README.txt position" do
|
53
|
+
@choctop.files['README.txt'][:position].should == [50, 100]
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "+ prepare_files" do
|
57
|
+
before(:each) do
|
58
|
+
FileUtils.chdir(File.dirname(__FILE__) + "/../features/fixtures/SampleApp") do
|
59
|
+
@choctop.prepare_files
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should have SampleApp.app in build/Release/dmg ready for inclusion in DMG" do
|
64
|
+
FileUtils.chdir(File.dirname(__FILE__) + "/../features/fixtures/SampleApp") do
|
65
|
+
File.should be_exists('build/Release/dmg/SampleApp.app')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should position SampleApp.app at [175, 65]" do
|
70
|
+
@choctop.set_position_of_files.should =~ /set position of item "SampleApp.app" to \{175, 65\}/
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should have README.txt in build/Release ready for inclusion in DMG" do
|
74
|
+
FileUtils.chdir(File.dirname(__FILE__) + "/../features/fixtures/SampleApp") do
|
75
|
+
File.should be_exists('build/Release/dmg/README.txt')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should position README.txt at [50, 100]" do
|
80
|
+
@choctop.set_position_of_files.should =~ /set position of item "README.txt" to \{50, 100\}/
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|