hawk 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt CHANGED
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
21
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,29 +1,62 @@
1
1
  # Hawk
2
2
 
3
- TODO: Write a gem description
3
+ Hawk is a simple iOS ad-hoc distribution tool. Deployments are lightweight,
4
+ backed by S3 storage (sourced from your own AWS account), and notifications of
5
+ new builds happen via email. Beyond a couple of S3 buckets, the only moving
6
+ parts to hawk are local on your machine. Hawk is a lightweight, low-friction way
7
+ to make ad-hoc deploys easy.
4
8
 
5
- ## Installation
9
+ ## Status
10
+
11
+ Hawk is alpha quality software at this point, although I'm rapidly working
12
+ towards firming up a 1.0 release in the next few weeks (by mid Feb 2013). Some
13
+ features that will be coming in the near future include
6
14
 
7
- Add this line to your application's Gemfile:
15
+ ### Planned for 0.2
16
+ * Automatic object expiry on S3 for .ipa and plist bundles
17
+ * Support for Xcode workspace files
8
18
 
9
- gem 'hawk'
19
+ ### Planned for 0.3
20
+ * Automatic bucket name selection option
21
+ * Tighter ACL support on uploaded files
10
22
 
11
- And then execute:
23
+ ### Planned for 0.4
24
+ * UDID validation
25
+ * Internal refactor
26
+
27
+ ## Installation
12
28
 
13
- $ bundle
29
+ Install hawk on your machine by either running `gem install hawk`, or else by
30
+ including it in your application's `Gemfile`. Once it's installed run
14
31
 
15
- Or install it yourself as:
32
+ $ hawkify
16
33
 
17
- $ gem install hawk
34
+ in your application's top-level directory to create a `Hawkfile` (if one doesn't
35
+ already exist). Open up `Hawkfile` in your editor of choice and follow the
36
+ comments therein to set it up to your liking.
18
37
 
19
38
  ## Usage
20
39
 
21
- TODO: Write usage instructions here
40
+ Once your `Hawkfile` is customized, you're ready to go. You can create and
41
+ deploy an ad-hoc build of your app by running
42
+
43
+ $ hawk
44
+
45
+ from any folder inside your application. This will do the following:
46
+
47
+ 1. Build your app for distribution (using the `Release` build configuration)
48
+ 2. Sign your app for ad-hoc distribution (according to the `Release` code signing identity in your project)
49
+ 3. Upload your app and some associated metadata to S3, using the credentials you
50
+ specify in your `Hawkfile`
51
+ 4. Draft an email with a pre-populated body (once again taken from your `Hawkfile`)
52
+ and open it using your local mail application, ready for you to edit and send at
53
+ your leisure
54
+
55
+ Hawk automatically includes any required provisioning profiles inside the
56
+ deployed app, so users don't need to do anything beyond clicking on the link
57
+ that hawk sends out. There's no client-side app to install, no servers to run,
58
+ no accounts to set up. It just works.
22
59
 
23
60
  ## Contributing
24
61
 
25
- 1. Fork it
26
- 2. Create your feature branch (`git checkout -b my-new-feature`)
27
- 3. Commit your changes (`git commit -am 'Add some feature'`)
28
- 4. Push to the branch (`git push origin my-new-feature`)
29
- 5. Create new Pull Request
62
+ Contributions welcome! Fork this repo and submit a pull request (or just open up a ticket and I'll see what I can do).
data/bin/hawk ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hawk'
4
+
5
+ Hawk::CLI.run(ARGV)
data/bin/hawkify ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hawk'
4
+
5
+ Hawk::Hawkifier.new(Dir.pwd).hawkify
data/hawk.gemspec CHANGED
@@ -18,4 +18,6 @@ Gem::Specification.new do |gem|
18
18
  gem.require_paths = ["lib"]
19
19
 
20
20
  gem.add_dependency('aws-sdk', '~> 1.8')
21
+ gem.add_dependency('xcodeproj', '~> 0.4')
22
+ gem.add_dependency('osx-plist', '~> 1.0')
21
23
  end
@@ -0,0 +1,82 @@
1
+ require 'tmpdir'
2
+ require 'osx/plist'
3
+
4
+ module Hawk
5
+ module Builder
6
+ module DSL
7
+ def signing_identity(identity)
8
+ @signing_identity = identity
9
+ end
10
+ end
11
+
12
+ def app_name
13
+ info_plist_property('CFBundleDisplayName')
14
+ end
15
+
16
+ def app_version
17
+ info_plist_property('CFBundleVersion')
18
+ end
19
+
20
+ def bundle_identifier
21
+ info_plist_property('CFBundleIdentifier')
22
+ end
23
+
24
+ def repo_version
25
+ `git rev-parse --short HEAD`.chomp
26
+ end
27
+
28
+ def ipa_file
29
+ if (!@ipa_file)
30
+ output_dir = Dir.tmpdir
31
+ product_name = project_property('PRODUCT_NAME')
32
+ @ipa_file = File.join(output_dir, "#{product_name}.ipa")
33
+
34
+ the_app_file = app_file # Do this before below so output happens in the right order
35
+
36
+ print "Signing app..."
37
+ output = `/usr/bin/xcrun -sdk iphoneos PackageApplication -v -s "#{@signing_identity || "iPhone Distribution"}" -o #{@ipa_file} #{the_app_file} 2>&1`
38
+ if $?.to_i != 0
39
+ puts "error (text follows)"
40
+ abort output
41
+ end
42
+ puts "done"
43
+ end
44
+ @ipa_file
45
+ end
46
+
47
+ def build_plist
48
+ ERB.new(File.read(File.join(File.dirname(__FILE__), '..', '..', 'templates', 'manifest.plist.erb'))).result(binding)
49
+ end
50
+
51
+ def itms_url
52
+ "itms-services://?action=download-manifest&url=#{plist_url}"
53
+ end
54
+
55
+ private
56
+
57
+ def app_file
58
+ if (!@app_file)
59
+ output_dir = Dir.tmpdir
60
+
61
+ print "Building Xcode project..."
62
+ output = `/usr/bin/xcodebuild CONFIGURATION_BUILD_DIR=#{output_dir} 2>&1`
63
+ if $?.to_i != 0
64
+ puts "error (text follows)"
65
+ abort output
66
+ end
67
+ puts "done"
68
+ @app_file = File.join(output_dir, project_property('FULL_PRODUCT_NAME'))
69
+ end
70
+ @app_file
71
+ end
72
+
73
+ def info_plist_property(prop)
74
+ plist_file = File.join(File.dirname(app_file), project_property('INFOPLIST_PATH'))
75
+ result = OSX::PropertyList.load_file(plist_file)[prop]
76
+ end
77
+
78
+ def project_property(prop)
79
+ `/usr/bin/xcodebuild -showBuildSettings | grep "\s#{prop} = "`.gsub!(/.* = /, '').chomp
80
+ end
81
+ end
82
+ end
data/lib/hawk/cli.rb ADDED
@@ -0,0 +1,25 @@
1
+ module Hawk
2
+ module CLI
3
+ def self.run(args)
4
+ hawkfile = closest_hawkfile(Dir.pwd)
5
+ if (hawkfile)
6
+ Dir.chdir(File.dirname(hawkfile))
7
+ Hawk::DSL.load(hawkfile)
8
+ else
9
+ puts "Cannot find hawkfile"
10
+ end
11
+ end
12
+
13
+ def self.closest_hawkfile(dir)
14
+ file_name = File.join(dir, 'Hawkfile')
15
+ if File.exists?(file_name)
16
+ file_name
17
+ elsif dir == '/'
18
+ nil
19
+ else
20
+ closest_hawkfile(File.expand_path(File.join(dir, '..')))
21
+ end
22
+ end
23
+ end
24
+ end
25
+
data/lib/hawk/dsl.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'hawk/builder'
2
+ require 'hawk/s3_uploader'
3
+ require 'hawk/notifier'
4
+
5
+ module Hawk
6
+ class DSL
7
+ include Hawk::Builder::DSL
8
+ include Hawk::S3Uploader::DSL
9
+ include Hawk::Notifier::DSL
10
+
11
+ def self.load(file)
12
+ instance = self.new
13
+ instance.instance_eval(File.read(file), file)
14
+
15
+ instance.execute
16
+ end
17
+
18
+ def execute
19
+ extend Hawk::Builder
20
+ extend Hawk::S3Uploader
21
+ extend Hawk::Notifier
22
+
23
+ notify_users # notify_users will trigger all dependent actions
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ require 'erb'
2
+
3
+ module Hawk
4
+ class Hawkifier
5
+ def initialize(dir)
6
+ @dir = dir
7
+ end
8
+
9
+ def hawkify
10
+ files.each do |name, contents|
11
+ write_file_if_not_exist(name, contents)
12
+ end
13
+ puts "hawkify done"
14
+ end
15
+
16
+ private
17
+ def files
18
+ { 'Hawkfile' => ERB.new(File.read(File.join(File.dirname(__FILE__), '..', '..', 'templates', 'Hawkfile.erb'))).result }
19
+ end
20
+
21
+ def write_file_if_not_exist(name, contents)
22
+ file = File.join(@dir, name)
23
+ if (File.exists?(file))
24
+ puts "hawkify skipping #{name}; file exists"
25
+ else
26
+ File.open(file, 'w') do |f|
27
+ puts "hawkify writing #{name}"
28
+ f.write(contents)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ require 'uri'
2
+
3
+ module Hawk
4
+ module Notifier
5
+ module DSL
6
+ def user(user)
7
+ puts "Warning: UDID check not implemented yet" if user.is_a? Hash
8
+ user = { user => nil } unless (user.is_a? Hash)
9
+ @users ||= {}
10
+ @users.merge!(user)
11
+ end
12
+
13
+ def email_subject(subject)
14
+ @email_subject = subject
15
+ end
16
+
17
+ def email_body(body)
18
+ @email_body = body
19
+ end
20
+ end
21
+
22
+ def notify_users
23
+ subject = URI.encode(ERB.new(@email_subject).result(binding)).gsub('?','%3F').gsub('&','%26')
24
+ body = URI.encode(ERB.new(@email_body).result(binding)).gsub('?','%3F').gsub('&','%26')
25
+ `open "mailto:?bcc=#{@users.keys.join(',')}&subject=#{subject}&body=#{body}"`
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,54 @@
1
+ require 'aws/s3'
2
+
3
+ module Hawk
4
+ module S3Uploader
5
+ module DSL
6
+ def access_key_id(key)
7
+ @access_key_id = key
8
+ end
9
+
10
+ def secret_access_key(key)
11
+ @secret_access_key = key
12
+ end
13
+
14
+ def bucket_name(name)
15
+ @bucket_name = name
16
+ end
17
+ end
18
+
19
+ def ipa_url
20
+ if (!@ipa_url)
21
+ file = ipa_file
22
+ @ipa_url = object(File.basename(file)) do |obj|
23
+ print "Uploading #{File.basename(file)} to S3..."
24
+ obj.write(Pathname.new(file))
25
+ puts 'done'
26
+ end
27
+ end
28
+ @ipa_url
29
+ end
30
+
31
+ def plist_url
32
+ if (!@plist_url)
33
+ @plist_url = object('manifest.plist') do |obj|
34
+ plist_data = build_plist
35
+ print 'Uploading plist to S3...'
36
+ obj.write(plist_data, :content_type => 'application/xml')
37
+ puts 'done'
38
+ end
39
+ end
40
+ @plist_url
41
+ end
42
+
43
+ private
44
+
45
+ def object(name, &block)
46
+ s3 = AWS::S3.new(:access_key_id => @access_key_id, :secret_access_key => @secret_access_key)
47
+ bucket = s3.buckets.create @bucket_name
48
+ obj = bucket.objects[name]
49
+ yield obj
50
+ obj.acl = :public_read
51
+ obj.public_url
52
+ end
53
+ end
54
+ end
data/lib/hawk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Hawk
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/hawk.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  require "hawk/version"
2
-
3
- module Hawk
4
- # Your code goes here...
5
- end
2
+ require "hawk/hawkifier"
3
+ require "hawk/cli"
4
+ require "hawk/dsl"
@@ -0,0 +1,60 @@
1
+ #
2
+ # Hawkfile
3
+ #
4
+
5
+ #
6
+ # XCode project information - used to configure how your app is built and signed
7
+ #
8
+ # The signing identity to sign your app with. Defaults to 'iPhone Distribution'
9
+ #signing_identity 'iPhone Distribution'
10
+
11
+ #
12
+ # AWS Credentials - used to upload your app and associated metadata into S3 so
13
+ # that users can download and install your app
14
+ #
15
+ #access_key_id 'AAAAAAAAAAAAAAAAAAAA'
16
+ #secret_access_key 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
17
+ #bucket_name 'mah-bukkit'
18
+
19
+ #
20
+ # User list - a list of users to send emails to upon successsful upload of a new
21
+ # version of your app. Can be specified as simple email addresses, or optionally
22
+ # as a hash with the value being the UDID of their device. Any specified UDIDs
23
+ # will be checked against the built app, to verify that they are entitled to run
24
+ # the app. Any failures will result in a warning being displayed
25
+ #
26
+ #user 'alice@example.com'
27
+ #user 'bob@example.com'
28
+ #user 'claire@example.com' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
29
+
30
+ #
31
+ # Notification settings - parameters describing the email to be sent out. This
32
+ # email will be opened locally on this machine by invoking a 'mailto:' url. Your
33
+ # default mail application will open, allowing you to review and edit the email
34
+ # before sending it. This is usually a good time to describe changes to the app,
35
+ # or to suggest areas for testing and review.
36
+ #
37
+ # The following tags are allowed for replacement. In addition to these tags, any
38
+ # ERB compliant tags will be replaced as well.
39
+ #
40
+ # <%= app_name %> : The name of the application, taken from the project
41
+ # <%= app_version %> : The current version of the project, taken from the project's
42
+ # Info.plist file
43
+ # <%= repo_version %> : An SCM specific version number taken from the current state
44
+ # of the project
45
+ # <%= itms_url %> : The itms-services: url that users will click on to install
46
+ # the application
47
+ #
48
+
49
+ email_subject "A new update of <%= app_name %> is available for download"
50
+
51
+ email_body <<EOF
52
+ A new build of <%= app_name %> is ready to install. Click the followling link
53
+ on your iOS device to install it:
54
+
55
+ <%= itms_url %>
56
+
57
+ Please reference '<%= repo_version %>' in any bug reports to help us out.
58
+
59
+ Thanks!
60
+ EOF
@@ -0,0 +1,31 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>items</key>
6
+ <array>
7
+ <dict>
8
+ <key>assets</key>
9
+ <array>
10
+ <dict>
11
+ <key>kind</key>
12
+ <string>software-package</string>
13
+ <key>url</key>
14
+ <string><%= ipa_url %></string>
15
+ </dict>
16
+ </array>
17
+ <key>metadata</key>
18
+ <dict>
19
+ <key>bundle-identifier</key>
20
+ <string><%= bundle_identifier %></string>
21
+ <key>bundle-version</key>
22
+ <string><%= app_version %></string>
23
+ <key>kind</key>
24
+ <string>software</string>
25
+ <key>title</key>
26
+ <string><%= app_name %></string>
27
+ </dict>
28
+ </dict>
29
+ </array>
30
+ </dict>
31
+ </plist>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hawk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-09 00:00:00.000000000 Z
12
+ date: 2013-02-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: aws-sdk
@@ -27,10 +27,44 @@ dependencies:
27
27
  - - ~>
28
28
  - !ruby/object:Gem::Version
29
29
  version: '1.8'
30
+ - !ruby/object:Gem::Dependency
31
+ name: xcodeproj
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '0.4'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '0.4'
46
+ - !ruby/object:Gem::Dependency
47
+ name: osx-plist
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
30
62
  description: A simple iOS ad-hoc distribution tool
31
63
  email:
32
64
  - mat@geeky.net
33
- executables: []
65
+ executables:
66
+ - hawk
67
+ - hawkify
34
68
  extensions: []
35
69
  extra_rdoc_files: []
36
70
  files:
@@ -39,9 +73,19 @@ files:
39
73
  - LICENSE.txt
40
74
  - README.md
41
75
  - Rakefile
76
+ - bin/hawk
77
+ - bin/hawkify
42
78
  - hawk.gemspec
43
79
  - lib/hawk.rb
80
+ - lib/hawk/builder.rb
81
+ - lib/hawk/cli.rb
82
+ - lib/hawk/dsl.rb
83
+ - lib/hawk/hawkifier.rb
84
+ - lib/hawk/notifier.rb
85
+ - lib/hawk/s3_uploader.rb
44
86
  - lib/hawk/version.rb
87
+ - templates/Hawkfile.erb
88
+ - templates/manifest.plist.erb
45
89
  homepage: http://github.com/mtrudel/hawk
46
90
  licenses: []
47
91
  post_install_message: