xcoder 0.1.15 → 0.1.18
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rbenv-version +1 -0
- data/.rvmrc +1 -1
- data/Gemfile +10 -2
- data/README.md +110 -9
- data/Rakefile +2 -2
- data/bin/xcoder +74 -0
- data/lib/xcode/builder.rb +1 -2
- data/lib/xcode/builder/base_builder.rb +231 -102
- data/lib/xcode/builder/build_parser.rb +146 -0
- data/lib/xcode/builder/project_target_config_builder.rb +2 -2
- data/lib/xcode/builder/scheme_builder.rb +29 -12
- data/lib/xcode/buildspec.rb +286 -0
- data/lib/xcode/configuration_list.rb +24 -24
- data/lib/xcode/deploy/ftp.rb +56 -0
- data/lib/xcode/deploy/kickfolio.rb +18 -0
- data/lib/xcode/deploy/s3.rb +38 -0
- data/lib/xcode/deploy/ssh.rb +43 -0
- data/lib/xcode/deploy/templates/index.rhtml +22 -0
- data/lib/xcode/deploy/templates/manifest.rhtml +31 -0
- data/lib/xcode/deploy/testflight.rb +32 -27
- data/lib/xcode/deploy/web_assets.rb +39 -0
- data/lib/xcode/info_plist.rb +16 -0
- data/lib/xcode/keychain.rb +33 -10
- data/lib/xcode/platform.rb +65 -0
- data/lib/xcode/project.rb +7 -3
- data/lib/xcode/provisioning_profile.rb +38 -2
- data/lib/xcode/scheme.rb +44 -17
- data/lib/xcode/shell/command.rb +79 -5
- data/lib/xcode/terminal_output.rb +116 -0
- data/lib/xcode/test/formatters/junit_formatter.rb +7 -2
- data/lib/xcode/test/formatters/stdout_formatter.rb +34 -25
- data/lib/xcode/test/parsers/kif_parser.rb +87 -0
- data/lib/xcode/test/parsers/ocunit_parser.rb +3 -3
- data/lib/xcode/version.rb +1 -1
- data/lib/xcode/workspace.rb +13 -5
- data/lib/xcoder.rb +33 -31
- data/spec/TestProject/TestProject.xcodeproj/project.pbxproj +1627 -1015
- data/spec/TestWorkspace.xcworkspace/contents.xcworkspacedata +7 -0
- data/spec/builder_spec.rb +87 -71
- data/spec/deploy_spec.rb +63 -0
- data/spec/ocunit_parser_spec.rb +1 -1
- data/xcoder.gemspec +3 -1
- metadata +95 -19
- data/lib/xcode/buildfile.rb +0 -101
- data/lib/xcode/shell.rb +0 -26
- data/spec/deploy_testflight_spec.rb +0 -27
@@ -1,9 +1,9 @@
|
|
1
1
|
module Xcode
|
2
2
|
module ConfigurationList
|
3
|
-
|
4
|
-
#
|
3
|
+
|
4
|
+
#
|
5
5
|
# @example configuration list
|
6
|
-
#
|
6
|
+
#
|
7
7
|
# 7165D47D146B4EA100DE2F0E /* Build configuration list for PBXNativeTarget "TestProject" */ = {
|
8
8
|
# isa = XCConfigurationList;
|
9
9
|
# buildConfigurations = (
|
@@ -13,73 +13,73 @@ module Xcode
|
|
13
13
|
# defaultConfigurationIsVisible = 0;
|
14
14
|
# defaultConfigurationName = Release;
|
15
15
|
# };
|
16
|
-
def self.
|
16
|
+
def self.configuration_list
|
17
17
|
{ 'isa' => 'XCConfigurationList',
|
18
18
|
'buildConfigurations' => [],
|
19
19
|
'defaultConfigurationIsVisible' => '0',
|
20
20
|
'defaultConfigurationName' => '' }
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
#
|
24
24
|
# @return [Hash] a hash of symbol names to configuration names.
|
25
|
-
#
|
25
|
+
#
|
26
26
|
def self.symbol_config_name_to_config_name
|
27
27
|
{ :debug => 'Debug', :release => 'Release' }
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
#
|
31
31
|
# Create a configuration for this ConfigurationList. This configuration needs
|
32
32
|
# to have a name.
|
33
|
-
#
|
33
|
+
#
|
34
34
|
# @note unique names are currently not enforced but likely necessary for the
|
35
35
|
# the target to be build successfully.
|
36
|
-
#
|
36
|
+
#
|
37
37
|
# @param [Types] name Description
|
38
38
|
#
|
39
39
|
def create_config(name)
|
40
|
-
|
40
|
+
|
41
41
|
name = ConfigurationList.symbol_config_name_to_config_name[name] if ConfigurationList.symbol_config_name_to_config_name[name]
|
42
|
-
|
43
|
-
# @todo a configuration has additional fields that are ususally set with
|
42
|
+
|
43
|
+
# @todo a configuration has additional fields that are ususally set with
|
44
44
|
# some target information for the title.
|
45
|
-
|
45
|
+
|
46
46
|
new_config = @registry.add_object(Configuration.default_properties(name))
|
47
47
|
@properties['buildConfigurations'] << new_config.identifier
|
48
|
-
|
48
|
+
|
49
49
|
yield new_config if block_given?
|
50
|
-
|
50
|
+
|
51
51
|
new_config.save!
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
#
|
55
55
|
# @return [BuildConfiguration] the build configuration that is set to default;
|
56
56
|
# nil if no configuration has been set as default.
|
57
|
-
#
|
57
|
+
#
|
58
58
|
def default_config
|
59
59
|
build_configurations.find {|config| config.name == default_configuration_name }
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
#
|
63
63
|
# @return [String] the name of the default build configuration; nil if no
|
64
64
|
# configuration has been set as default.
|
65
|
-
#
|
65
|
+
#
|
66
66
|
def default_config_name
|
67
67
|
default_configuration_name
|
68
68
|
end
|
69
|
-
|
69
|
+
|
70
70
|
#
|
71
71
|
# @todo allow the ability for a configuration to set itself as default and/or
|
72
72
|
# let a configuration be specified as a parameter here. Though we need
|
73
73
|
# to check to see that the configuration is part of the this configuration
|
74
74
|
# list.
|
75
|
-
#
|
76
|
-
# @param [String] name of the build configuration to set as the default
|
75
|
+
#
|
76
|
+
# @param [String] name of the build configuration to set as the default
|
77
77
|
# configuration; specify nil if you want to remove any default configuration.
|
78
78
|
#
|
79
79
|
def set_default_config(name)
|
80
80
|
# @todo ensure that the name specified is one of the available configurations
|
81
81
|
@properties['defaultConfigurationName'] = name
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
84
|
end
|
85
|
-
end
|
85
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'net/ftp'
|
2
|
+
require 'xcode/deploy/web_assets'
|
3
|
+
|
4
|
+
module Xcode
|
5
|
+
module Deploy
|
6
|
+
class Ftp
|
7
|
+
attr_accessor :host, :username, :password, :dir
|
8
|
+
|
9
|
+
def initialize(builder, options = {})
|
10
|
+
@builder = builder
|
11
|
+
@username = options[:username]
|
12
|
+
@password = options[:password]
|
13
|
+
@dir = options[:dir]
|
14
|
+
@host = options[:host]
|
15
|
+
@base_url = options[:base_url]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Support templating of member data.
|
19
|
+
def get_binding
|
20
|
+
binding
|
21
|
+
end
|
22
|
+
|
23
|
+
def remote_installation_path
|
24
|
+
File.join(@dir, @builder.product_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def deploy
|
28
|
+
WebAssets.generate @builder, @base_url do |dir|
|
29
|
+
puts "Connecting to #{@remote_host} with username #{@username}"
|
30
|
+
Net::FTP.open(@host, @username, @password) do |ftp|
|
31
|
+
begin
|
32
|
+
puts "Creating folder #{remote_installation_path}"
|
33
|
+
ftp.mkdir(remote_installation_path)
|
34
|
+
rescue Net::FTPError
|
35
|
+
puts "It looks like the folder is already there."
|
36
|
+
end
|
37
|
+
|
38
|
+
puts "Changing to remote folder #{remote_installation_path}"
|
39
|
+
files = ftp.chdir(remote_installation_path)
|
40
|
+
|
41
|
+
Dir["#{dir}/*"].each do |f|
|
42
|
+
filename = File.basename(f)
|
43
|
+
puts "Uploading #{filename}"
|
44
|
+
ftp.putbinaryfile(f, filename, 1024)
|
45
|
+
end
|
46
|
+
|
47
|
+
filename = File.basename("#{@builder.ipa_path}")
|
48
|
+
puts "Uploading #{filename}"
|
49
|
+
ftp.putbinaryfile("#{@builder.ipa_path}", filename, 1024)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Xcode
|
2
|
+
module Deploy
|
3
|
+
class Kickfolio
|
4
|
+
|
5
|
+
def initialize(bundler, options={})
|
6
|
+
@bundler = bundler
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def deploy
|
11
|
+
RestClient.post "https://kickfolio.com/api/apps/#{@options[:app_id]}",
|
12
|
+
{:bundle_url => @options[:url], :auth_token => @options[:api_key]},
|
13
|
+
:content_type => 'application/json'
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'xcode/deploy/web_assets'
|
2
|
+
require 'aws-sdk'
|
3
|
+
|
4
|
+
module Xcode
|
5
|
+
module Deploy
|
6
|
+
class S3
|
7
|
+
def initialize(builder, options)
|
8
|
+
@builder = builder
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def bucket
|
13
|
+
return @bucket unless @bucket.nil?
|
14
|
+
s3 = AWS::S3.new @options
|
15
|
+
@bucket = s3.buckets.create(@options[:bucket]) rescue s3.buckets[@options[:bucket]]
|
16
|
+
@bucket
|
17
|
+
end
|
18
|
+
|
19
|
+
def upload(path)
|
20
|
+
obj_path = File.join(@options[:dir]||'', File.basename(path))
|
21
|
+
puts "Uploading #{path} => #{bucket.name}/#{obj_path}"
|
22
|
+
bucket.objects[obj_path].
|
23
|
+
write(File.open(path), :acl => :public_read)
|
24
|
+
end
|
25
|
+
|
26
|
+
def deploy
|
27
|
+
remote_ipa = upload @builder.ipa_path
|
28
|
+
base_url = remote_ipa.public_url(:secure => false).to_s.split("/")[0..-2].join("/")
|
29
|
+
|
30
|
+
WebAssets.generate @builder, base_url do |dir|
|
31
|
+
Dir["#{dir}/*"].each do |path|
|
32
|
+
upload path
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'net/scp'
|
3
|
+
require 'xcode/deploy/web_assets'
|
4
|
+
|
5
|
+
module Xcode
|
6
|
+
module Deploy
|
7
|
+
class Ssh
|
8
|
+
attr_accessor :host, :username, :password, :dir
|
9
|
+
|
10
|
+
def initialize(builder, options)
|
11
|
+
@builder = builder
|
12
|
+
@username = options[:username]
|
13
|
+
@password = options[:password]
|
14
|
+
@dir = options[:dir]
|
15
|
+
@host = options[:host]
|
16
|
+
@base_url = options[:base_url]
|
17
|
+
end
|
18
|
+
|
19
|
+
def remote_installation_path
|
20
|
+
File.join(@dir, @builder.product_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def deploy
|
24
|
+
WebAssets.generate @builder, @base_url do |dist_path|
|
25
|
+
puts "Copying files to #{@remote_host}:#{remote_installation_path}"
|
26
|
+
Net::SSH.start(@host, @username, :password => @password) do |ssh|
|
27
|
+
puts "Creating folder with mkdir #{remote_installation_path}"
|
28
|
+
ssh.exec!("mkdir #{remote_installation_path}")
|
29
|
+
end
|
30
|
+
Net::SCP.start(@host, @username, :password => @password) do |scp|
|
31
|
+
puts "Copying files from folder #{dist_path}"
|
32
|
+
Dir["#{dist_path}/*"].each do |f|
|
33
|
+
puts "Copying #{f} to remote host in folder #{remote_installation_path}"
|
34
|
+
scp.upload! "#{f}", "#{remote_installation_path}"
|
35
|
+
end
|
36
|
+
scp.upload! "#{@options[:ipa_path]}", "#{remote_installation_path}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
2
|
+
<html xmlns="http://www.w3.org/1999/xhtml">
|
3
|
+
<head>
|
4
|
+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
|
6
|
+
<title>Beta Download</title>
|
7
|
+
<style type="text/css">
|
8
|
+
body {background:#fff;margin:0;padding:0;font-family:arial,helvetica,sans-serif;text-align:center;padding:10px;color:#333;font-size:16px;}
|
9
|
+
#container {width:300px;margin:0 auto;}
|
10
|
+
h1 {margin:0;padding:0;font-size:14px;}
|
11
|
+
p {font-size:13px;}
|
12
|
+
.link {background:#ecf5ff;border-top:1px solid #fff;border:1px solid #dfebf8;margin-top:.5em;padding:.3em;}
|
13
|
+
.link a {text-decoration:none;font-size:15px;display:block;color:#069;}
|
14
|
+
</style>
|
15
|
+
</head>
|
16
|
+
<body>
|
17
|
+
<div id="container">
|
18
|
+
<div class="link"><a href="itms-services://?action=download-manifest&url=<%= manifest_url %>">Tap Here to Install<br /><%= product_name %><br />On Your Device</a></div>
|
19
|
+
<p><strong>Link didn't work?</strong><br />
|
20
|
+
Make sure you're visiting this page on your device, not your computer.</p>
|
21
|
+
</body>
|
22
|
+
</html>
|
@@ -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><%= deployment_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><%= bundle_version %></string>
|
23
|
+
<key>kind</key>
|
24
|
+
<string>software</string>
|
25
|
+
<key>title</key>
|
26
|
+
<string><%= product_name %></string>
|
27
|
+
</dict>
|
28
|
+
</dict>
|
29
|
+
</array>
|
30
|
+
</dict>
|
31
|
+
</plist>
|
@@ -1,60 +1,65 @@
|
|
1
1
|
require 'rest-client'
|
2
|
-
require 'json'
|
3
2
|
|
4
3
|
module Xcode
|
5
4
|
module Deploy
|
6
5
|
class Testflight
|
7
|
-
attr_accessor :api_token, :team_token, :notify, :proxy, :notes, :lists
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
attr_accessor :api_token, :team_token, :notify, :proxy, :notes, :lists, :builder
|
7
|
+
@@defaults = {}
|
8
|
+
|
9
|
+
def self.defaults(defaults={})
|
10
|
+
@@defaults = defaults
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(builder, options={})
|
14
|
+
@builder = builder
|
15
|
+
@api_token = options[:api_token]||@@defaults[:api_token]
|
16
|
+
@team_token = options[:team_token]||@@defaults[:team_token]
|
17
|
+
@notify = options.has_key?(:notify) ? options[:notify] : true
|
18
|
+
@notes = options[:notes]
|
19
|
+
@lists = options[:lists]||[]
|
15
20
|
@proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
|
16
21
|
end
|
17
|
-
|
18
|
-
def
|
22
|
+
|
23
|
+
def deploy
|
19
24
|
puts "Uploading to Testflight..."
|
20
|
-
|
25
|
+
|
21
26
|
# RestClient.proxy = @proxy || ENV['http_proxy'] || ENV['HTTP_PROXY']
|
22
27
|
# RestClient.log = '/tmp/restclient.log'
|
23
|
-
#
|
28
|
+
#
|
24
29
|
# response = RestClient.post('http://testflightapp.com/api/builds.json',
|
25
|
-
# :file => File.new(ipa_path),
|
26
|
-
# :dsym => File.new(
|
30
|
+
# :file => File.new(builder.ipa_path),
|
31
|
+
# :dsym => File.new(builder.dsym_zip_path),
|
27
32
|
# :api_token => @api_token,
|
28
33
|
# :team_token => @team_token,
|
29
34
|
# :notes => @notes,
|
30
35
|
# :notify => @notify ? 'True' : 'False',
|
31
36
|
# :distribution_lists => @lists.join(',')
|
32
37
|
# )
|
33
|
-
#
|
38
|
+
#
|
34
39
|
# json = JSON.parse(response)
|
35
40
|
# puts " + Done, got: #{json.inspect}"
|
36
41
|
# json
|
37
|
-
|
42
|
+
|
38
43
|
cmd = Xcode::Shell::Command.new 'curl'
|
39
|
-
cmd << "--proxy #{@proxy}" unless @proxy.nil? or @proxy==''
|
44
|
+
cmd << "--proxy #{@proxy}" unless @proxy.nil? or @proxy==''
|
40
45
|
cmd << "-X POST http://testflightapp.com/api/builds.json"
|
41
|
-
cmd << "-F file=@\"#{ipa_path}\""
|
42
|
-
cmd << "-F dsym=@\"#{
|
46
|
+
cmd << "-F file=@\"#{@builder.ipa_path}\""
|
47
|
+
cmd << "-F dsym=@\"#{@builder.dsym_zip_path}\"" unless @builder.dsym_zip_path.nil?
|
43
48
|
cmd << "-F api_token='#{@api_token}'"
|
44
49
|
cmd << "-F team_token='#{@team_token}'"
|
45
50
|
cmd << "-F notes=\"#{@notes}\"" unless @notes.nil?
|
46
51
|
cmd << "-F notify=#{@notify ? 'True' : 'False'}"
|
47
52
|
cmd << "-F distribution_lists='#{@lists.join(',')}'" unless @lists.count==0
|
48
|
-
|
49
|
-
response =
|
50
|
-
|
51
|
-
json =
|
53
|
+
|
54
|
+
response = cmd.execute
|
55
|
+
|
56
|
+
json = MultiJson.load(response.join(''))
|
52
57
|
puts " + Done, got: #{json.inspect}"
|
53
|
-
|
58
|
+
|
54
59
|
yield(json) if block_given?
|
55
|
-
|
60
|
+
|
56
61
|
json
|
57
62
|
end
|
58
63
|
end
|
59
64
|
end
|
60
|
-
end
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
module Xcode
|
6
|
+
module Deploy
|
7
|
+
module WebAssets
|
8
|
+
class BindingContext < OpenStruct
|
9
|
+
def get_binding
|
10
|
+
return binding()
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.generate(builder, base_url, &block)
|
15
|
+
Dir.mktmpdir do |dist_path|
|
16
|
+
|
17
|
+
context = BindingContext.new
|
18
|
+
context.product_name = builder.product_name
|
19
|
+
context.manifest_url = "#{base_url}/manifest.plist"
|
20
|
+
context.deployment_url = "#{base_url}/#{builder.ipa_name}"
|
21
|
+
context.bundle_version = builder.bundle_version
|
22
|
+
context.bundle_identifier = builder.bundle_identifier
|
23
|
+
|
24
|
+
rhtml = ERB.new(File.read("#{File.dirname(__FILE__)}/templates/manifest.rhtml"))
|
25
|
+
File.open("#{dist_path}/manifest.plist", "w") do |io|
|
26
|
+
io.write(rhtml.result(context.get_binding))
|
27
|
+
end
|
28
|
+
|
29
|
+
rhtml = ERB.new(File.read("#{File.dirname(__FILE__)}/templates/index.rhtml"))
|
30
|
+
File.open("#{dist_path}/index.html", "w") do |io|
|
31
|
+
io.write(rhtml.result(context.get_binding))
|
32
|
+
end
|
33
|
+
|
34
|
+
yield dist_path
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|