xcoder 0.1.1 → 0.1.2
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 +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
|