xcoder 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/.rvmrc +4 -0
- data/Gemfile +14 -0
- data/Guardfile +14 -0
- data/README.md +142 -0
- data/Rakefile +28 -0
- data/lib/xcode/build_file.rb +22 -0
- data/lib/xcode/build_phase.rb +92 -0
- data/lib/xcode/builder.rb +190 -0
- data/lib/xcode/buildfile.rb +101 -0
- data/lib/xcode/configuration.rb +161 -0
- data/lib/xcode/configuration_list.rb +85 -0
- data/lib/xcode/core_ext/array.rb +23 -0
- data/lib/xcode/core_ext/boolean.rb +21 -0
- data/lib/xcode/core_ext/fixnum.rb +5 -0
- data/lib/xcode/core_ext/hash.rb +27 -0
- data/lib/xcode/core_ext/string.rb +26 -0
- data/lib/xcode/file_reference.rb +77 -0
- data/lib/xcode/group.rb +197 -0
- data/lib/xcode/info_plist.rb +41 -0
- data/lib/xcode/keychain.rb +122 -0
- data/lib/xcode/parsers/plutil_project_parser.rb +20 -0
- data/lib/xcode/project.rb +335 -0
- data/lib/xcode/provisioning_profile.rb +53 -0
- data/lib/xcode/registry.rb +168 -0
- data/lib/xcode/resource.rb +203 -0
- data/lib/xcode/scheme.rb +36 -0
- data/lib/xcode/shell.rb +22 -0
- data/lib/xcode/simple_identifier_generator.rb +17 -0
- data/lib/xcode/target.rb +204 -0
- data/lib/xcode/test/formatters/junit_formatter.rb +50 -0
- data/lib/xcode/test/ocunit_report_parser.rb +68 -0
- data/lib/xcode/test/suite_result.rb +31 -0
- data/lib/xcode/test/test_result.rb +38 -0
- data/lib/xcode/testflight.rb +56 -0
- data/lib/xcode/variant_group.rb +28 -0
- data/lib/xcode/version.rb +3 -0
- data/lib/xcode/workspace.rb +40 -0
- data/lib/xcoder.rb +105 -0
- data/xcoder.gemspec +26 -0
- metadata +52 -12
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
module Xcode
|
5
|
+
module Test
|
6
|
+
module Formatters
|
7
|
+
class JunitFormatter
|
8
|
+
def initialize(dir)
|
9
|
+
@dir = dir
|
10
|
+
end
|
11
|
+
|
12
|
+
def write(report)
|
13
|
+
if report.end_time.nil?
|
14
|
+
raise "Report #{report} #{report.name} has a nil end time!?"
|
15
|
+
end
|
16
|
+
xml = ::Builder::XmlMarkup.new( :indent => 2 )
|
17
|
+
xml.instruct! :xml, :encoding => "UTF-8"
|
18
|
+
xml.testsuite(:errors => report.total_error_tests,
|
19
|
+
:failures => report.total_failed_tests,
|
20
|
+
:hostname => Socket.gethostname,
|
21
|
+
:name => report.name,
|
22
|
+
:tests => report.tests.count,
|
23
|
+
:time => (report.end_time - report.start_time),
|
24
|
+
:timestamp => report.end_time
|
25
|
+
) do |p|
|
26
|
+
|
27
|
+
report.tests.each do |t|
|
28
|
+
p.testcase(:classname => report.name,
|
29
|
+
:name => t.name,
|
30
|
+
:time => t.time
|
31
|
+
) do |e|
|
32
|
+
|
33
|
+
if t.error?
|
34
|
+
e.failure t.error_location, :message => t.error_message, :type => 'Failure'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
File.open("#{@dir}/TEST-#{report.name}.xml", 'w') do |current_file|
|
41
|
+
current_file.write xml.target!
|
42
|
+
end
|
43
|
+
|
44
|
+
end # write
|
45
|
+
|
46
|
+
end # JUnitFormatter
|
47
|
+
|
48
|
+
end # Formatters
|
49
|
+
end # Test
|
50
|
+
end # Xcode
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'time'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'xcode/test/suite_result'
|
4
|
+
require 'xcode/test/test_result'
|
5
|
+
|
6
|
+
module Xcode
|
7
|
+
module Test
|
8
|
+
|
9
|
+
module Formatters
|
10
|
+
end
|
11
|
+
|
12
|
+
class OCUnitReportParser
|
13
|
+
|
14
|
+
attr_reader :exit_code, :reports
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@exit_code = 0
|
18
|
+
@reports = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def write(dir, format=:junit)
|
22
|
+
dir = File.expand_path(dir)
|
23
|
+
FileUtils.mkdir_p(dir)
|
24
|
+
|
25
|
+
require "xcode/test/formatters/#{format.to_s}_formatter"
|
26
|
+
formatter = Formatters.const_get("#{format.to_s.capitalize}Formatter").new(dir)
|
27
|
+
@reports.each do |r|
|
28
|
+
formatter.write(r)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def <<(piped_row)
|
33
|
+
case piped_row
|
34
|
+
|
35
|
+
when /Test Suite '(\S+)'.*started at\s+(.*)/
|
36
|
+
name = $1
|
37
|
+
time = Time.parse($2)
|
38
|
+
@reports << SuiteResult.new(name, time) unless name=~/\// # ignore if its a file path
|
39
|
+
|
40
|
+
when /Test Suite '(\S+)'.*finished at\s+(.*)./
|
41
|
+
@reports.last.finish(Time.parse($2))
|
42
|
+
|
43
|
+
when /Test Case '-\[\S+\s+(\S+)\]' started./
|
44
|
+
test = TestResult.new($1)
|
45
|
+
@reports.last.tests << test
|
46
|
+
|
47
|
+
when /Test Case '-\[\S+\s+(\S+)\]' passed \((.*) seconds\)/
|
48
|
+
@reports.last.tests.last.passed($2.to_f)
|
49
|
+
|
50
|
+
when /(.*): error: -\[(\S+) (\S+)\] : (.*)/
|
51
|
+
@reports.last.tests.last.error(error_message,error_location)
|
52
|
+
@exit_code = 1 # should terminate
|
53
|
+
|
54
|
+
when /Test Case '-\[\S+ (\S+)\]' failed \((\S+) seconds\)/
|
55
|
+
@reports.last.tests.last.failed($2.to_f)
|
56
|
+
@exit_code = 1 # should terminate
|
57
|
+
|
58
|
+
when /failed with exit code (\d+)/
|
59
|
+
@exit_code = $1.to_i
|
60
|
+
|
61
|
+
when
|
62
|
+
/BUILD FAILED/
|
63
|
+
@exit_code = -1;
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Xcode
|
2
|
+
module Test
|
3
|
+
class SuiteResult
|
4
|
+
attr_accessor :tests, :name, :start_time, :end_time
|
5
|
+
|
6
|
+
def initialize(name, start_time)
|
7
|
+
@name = name
|
8
|
+
@start_time = start_time
|
9
|
+
@tests = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def finish(time)
|
13
|
+
raise "Time is nil" if time.nil?
|
14
|
+
@end_time = time
|
15
|
+
end
|
16
|
+
|
17
|
+
def total_error_tests
|
18
|
+
@tests.select {|t| t.error? }.count
|
19
|
+
end
|
20
|
+
|
21
|
+
def total_passed_tests
|
22
|
+
@tests.select {|t| t.passed? }.count
|
23
|
+
end
|
24
|
+
|
25
|
+
def total_failed_tests
|
26
|
+
@tests.select {|t| t.failed? }.count
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Xcode
|
2
|
+
module Test
|
3
|
+
class TestResult
|
4
|
+
attr_reader :name, :time, :error_message, :error_location
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
end
|
9
|
+
|
10
|
+
def passed?
|
11
|
+
@passed
|
12
|
+
end
|
13
|
+
|
14
|
+
def failed?
|
15
|
+
error? or !@passed
|
16
|
+
end
|
17
|
+
|
18
|
+
def error?
|
19
|
+
!@error_message.nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def passed(time)
|
23
|
+
@passed = true
|
24
|
+
@time = time
|
25
|
+
end
|
26
|
+
|
27
|
+
def failed(time)
|
28
|
+
@passed = false
|
29
|
+
@time = time
|
30
|
+
end
|
31
|
+
|
32
|
+
def error(error_message,error_location)
|
33
|
+
@error_message = error_message
|
34
|
+
@error_location = error_location
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Xcode
|
5
|
+
class Testflight
|
6
|
+
attr_accessor :api_token, :team_token, :notify, :proxy, :notes, :lists
|
7
|
+
|
8
|
+
def initialize(api_token, team_token)
|
9
|
+
@api_token = api_token
|
10
|
+
@team_token = team_token
|
11
|
+
@notify = true
|
12
|
+
@notes = nil
|
13
|
+
@lists = []
|
14
|
+
@proxy = ENV['http_proxy'] || ENV['HTTP_PROXY']
|
15
|
+
end
|
16
|
+
|
17
|
+
def upload(ipa_path, dsymzip_path=nil)
|
18
|
+
puts "Uploading to Testflight..."
|
19
|
+
|
20
|
+
# RestClient.proxy = @proxy || ENV['http_proxy'] || ENV['HTTP_PROXY']
|
21
|
+
# RestClient.log = '/tmp/restclient.log'
|
22
|
+
#
|
23
|
+
# response = RestClient.post('http://testflightapp.com/api/builds.json',
|
24
|
+
# :file => File.new(ipa_path),
|
25
|
+
# :dsym => File.new(dsymzip_path),
|
26
|
+
# :api_token => @api_token,
|
27
|
+
# :team_token => @team_token,
|
28
|
+
# :notes => @notes,
|
29
|
+
# :notify => @notify ? 'True' : 'False',
|
30
|
+
# :distribution_lists => @lists.join(',')
|
31
|
+
# )
|
32
|
+
#
|
33
|
+
# json = JSON.parse(response)
|
34
|
+
# puts " + Done, got: #{json.inspect}"
|
35
|
+
# json
|
36
|
+
|
37
|
+
cmd = []
|
38
|
+
cmd << 'curl'
|
39
|
+
cmd << "--proxy #{@proxy}" unless @proxy.nil? or @proxy==''
|
40
|
+
cmd << "-X POST http://testflightapp.com/api/builds.json"
|
41
|
+
cmd << "-F file=@\"#{ipa_path}\""
|
42
|
+
cmd << "-F dsym=@\"#{dsymzip_path}\"" unless dsymzip_path.nil?
|
43
|
+
cmd << "-F api_token='#{@api_token}'"
|
44
|
+
cmd << "-F team_token='#{@team_token}'"
|
45
|
+
cmd << "-F notes=\"#{@notes}\"" unless @notes.nil?
|
46
|
+
cmd << "-F notify=#{@notify ? 'True' : 'False'}"
|
47
|
+
cmd << "-F distribution_lists='#{@lists.join(',')}'" unless @lists.count==0
|
48
|
+
|
49
|
+
response = Xcode::Shell.execute(cmd)
|
50
|
+
|
51
|
+
json = JSON.parse(response.join(''))
|
52
|
+
puts " + Done, got: #{json.inspect}"
|
53
|
+
json
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative 'group'
|
2
|
+
|
3
|
+
module Xcode
|
4
|
+
|
5
|
+
module VariantGroup
|
6
|
+
include Group ; extend Group
|
7
|
+
|
8
|
+
#
|
9
|
+
# # E21EB9DE14E357CF0058122A /* InfoPlist.strings */ = {
|
10
|
+
# isa = PBXVariantGroup;
|
11
|
+
# children = (
|
12
|
+
# E21EB9DF14E357CF0058122A /* en */,
|
13
|
+
# );
|
14
|
+
# name = InfoPlist.strings;
|
15
|
+
# sourceTree = "<group>";
|
16
|
+
# };
|
17
|
+
#
|
18
|
+
def self.info_plist properties
|
19
|
+
default_properties = { 'isa' => 'PBXVariantGroup',
|
20
|
+
'children' => [],
|
21
|
+
'name' => name,
|
22
|
+
'sourceTree' => '<group>' }
|
23
|
+
|
24
|
+
default_properties.merge(properties)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'xcode/project'
|
2
|
+
|
3
|
+
module Xcode
|
4
|
+
class Workspace
|
5
|
+
attr_reader :projects, :name, :path
|
6
|
+
def initialize(path)
|
7
|
+
path = "#{path}.xcworkspace" unless path=~/\.xcworkspace/
|
8
|
+
path = "#{path}/contents.xcworkspacedata" unless path=~/xcworkspacedata$/
|
9
|
+
|
10
|
+
@name = File.basename(path.gsub(/\.xcworkspace\/contents\.xcworkspacedata/,''))
|
11
|
+
@projects = []
|
12
|
+
@path = File.expand_path path
|
13
|
+
|
14
|
+
File.open(@path).read.split(/<FileRef/).each do |line|
|
15
|
+
if line=~/location\s*=\s*\"group\:(.+?)\"/
|
16
|
+
project_path = "#{workspace_root}/#{$1}"
|
17
|
+
@projects << Xcode::Project.new(project_path)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def project(name)
|
23
|
+
project = @projects.select {|c| c.name == name.to_s}.first
|
24
|
+
raise "No such project #{name}, available projects are #{@projects.map {|c| c.name}.join(', ')}" if project.nil?
|
25
|
+
yield project if block_given?
|
26
|
+
project
|
27
|
+
end
|
28
|
+
|
29
|
+
def describe
|
30
|
+
puts "Workspace #{name} contains:"
|
31
|
+
projects.each do |p|
|
32
|
+
p.describe
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def workspace_root
|
37
|
+
File.dirname(File.dirname(@path))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/xcoder.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'find'
|
2
|
+
require 'fileutils'
|
3
|
+
require "xcode/version"
|
4
|
+
require "xcode/project"
|
5
|
+
require "xcode/info_plist"
|
6
|
+
require "xcode/shell"
|
7
|
+
require 'plist'
|
8
|
+
require 'xcode/keychain'
|
9
|
+
require 'xcode/workspace'
|
10
|
+
require 'xcode/buildfile'
|
11
|
+
|
12
|
+
module Xcode
|
13
|
+
@@projects = nil
|
14
|
+
@@workspaces = nil
|
15
|
+
@@sdks = nil
|
16
|
+
|
17
|
+
def self.projects
|
18
|
+
@@projects = parse_projects if @@projects.nil?
|
19
|
+
@@projects
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.workspaces
|
23
|
+
@@workspaces = parse_workspaces if @@workspaces.nil?
|
24
|
+
@@workspaces
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.project(name)
|
28
|
+
name = name.to_s
|
29
|
+
|
30
|
+
return Xcode::Project.new(name) if name=~/\.xcodeproj/
|
31
|
+
|
32
|
+
self.projects.each do |p|
|
33
|
+
return p if p.name == name
|
34
|
+
end
|
35
|
+
raise "Unable to find a project named #{name}. However, I did find these projects: #{self.projects.map {|p| p.name}.join(', ') }"
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.workspace(name)
|
39
|
+
name = name.to_s
|
40
|
+
|
41
|
+
return Xcode::Workspace.new(name) if name=~/\.xcworkspace/
|
42
|
+
|
43
|
+
self.workspaces.each do |p|
|
44
|
+
return p if p.name == name
|
45
|
+
end
|
46
|
+
raise "Unable to find a workspace named #{name}. However, I did find these workspaces: #{self.workspaces.map {|p| p.name}.join(', ') }"
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.find_projects(dir='.')
|
50
|
+
parse_projects(dir)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.is_sdk_available?(sdk)
|
54
|
+
parse_sdks if @@sdks.nil?
|
55
|
+
@@sdks.values.include? sdk
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.available_sdks
|
59
|
+
parse_sdks if @@sdks.nil?
|
60
|
+
@@sdks
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def self.parse_sdks
|
65
|
+
@@sdks = {}
|
66
|
+
parsing = false
|
67
|
+
`xcodebuild -showsdks`.split("\n").each do |l|
|
68
|
+
l.strip!
|
69
|
+
if l=~/(.*)\s+SDKs:/
|
70
|
+
parsing = true
|
71
|
+
elsif l=~/^\s*$/
|
72
|
+
parsing = false
|
73
|
+
elsif parsing
|
74
|
+
l=~/([^\t]+)\t+\-sdk (.*)/
|
75
|
+
@@sdks[$1.strip] = $2.strip unless $1.nil? and $2.nil?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.parse_workspaces(dir='.')
|
81
|
+
projects = []
|
82
|
+
Find.find(dir) do |path|
|
83
|
+
if path=~/\.xcworkspace$/ and !(path=~/\.xcodeproj\//)
|
84
|
+
projects << Xcode::Workspace.new(path)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
projects
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.parse_projects(dir='.')
|
91
|
+
projects = []
|
92
|
+
Find.find(dir) do |path|
|
93
|
+
if path=~/(.*)\.xcodeproj$/
|
94
|
+
projects << Xcode::Project.new(path)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
projects
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
require 'xcode/core_ext/hash'
|
102
|
+
require 'xcode/core_ext/array'
|
103
|
+
require 'xcode/core_ext/string'
|
104
|
+
require 'xcode/core_ext/boolean'
|
105
|
+
require 'xcode/core_ext/fixnum'
|
data/xcoder.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "xcode/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "xcoder"
|
7
|
+
s.version = Xcode::VERSION
|
8
|
+
s.authors = ["Ray Hilton", "Frank Webber"]
|
9
|
+
s.email = ["ray@wirestorm.net", "franklin.webber@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/rayh/xcoder"
|
11
|
+
s.summary = %q{Ruby wrapper around xcodebuild, xcrun, agvtool and pbxproj files}
|
12
|
+
s.description = %q{Provides a ruby based object-model for parsing project structures and invoking builds}
|
13
|
+
|
14
|
+
s.rubyforge_project = "xcoder"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n").reject {|f| f=~/^(?:spec|examples)\//} # Ignore example files
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_runtime_dependency "json"
|
22
|
+
s.add_runtime_dependency "plist"
|
23
|
+
s.add_runtime_dependency "nokogiri"
|
24
|
+
s.add_runtime_dependency "builder"
|
25
|
+
s.add_runtime_dependency "rest-client"
|
26
|
+
end
|