xcoder 0.1.15 → 0.1.18
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 +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
|