xcoder 0.1.6 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,3 +1,5 @@
1
+ # Mac
2
+ .DS_Store
1
3
 
2
4
  # Dependency management
3
5
 
data/Gemfile CHANGED
@@ -1,6 +1,8 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gemspec
4
+ gem 'rake'
5
+
4
6
  gem 'builder'
5
7
  gem 'json'
6
8
  gem 'plist'
data/README.md CHANGED
@@ -43,7 +43,7 @@ You can either use the user's login keychain, another named keychain, or simply
43
43
 
44
44
  #### Creating a temporary keychain
45
45
 
46
- Xcode::Keychain.temp_keychain('ProjectKeychain.keychain') do |keychain|
46
+ Xcode::Keychain.temp do |keychain|
47
47
  # import certs into the keychain
48
48
  # perform builds within this keychain's context
49
49
  end # Keychain is deleted
@@ -130,10 +130,63 @@ You can also optionally set a .proxy= property or just set the HTTP_PROXY enviro
130
130
  You can invoke your test target/bundle from the builder
131
131
 
132
132
  builder.test do |report|
133
- report.write 'test-reports', :junit
133
+ report.debug = false # default false, set to true to see raw output from xcodebuild
134
+
135
+ # The following is the default setup, you wouldnt normally need to do this unless
136
+ # you want to add new formatters
137
+ report.formatters = []
138
+ report.add_formatter :junit, 'test-reports' # Output JUnit format results to test-reports/
139
+ report.add_formatter :stdout # Output a simplified output to STDOUT
134
140
  end
135
141
 
136
- This will invoke the test target, capture the output and write the junit reports to the test-reports directory. Currently only junit is supported.
142
+ This will invoke the test target, capture the output and write the junit reports to the test-reports directory. Currently only junit is supported, although you can write your own formatter quite easily (for an example, look at Xcode::Test::Formatters::JunitFormatter).
143
+
144
+ ## Rake Tasks
145
+
146
+ Xcoder provides a rake task to assist with make it easier to perform common Xcode project actions from the command-line.
147
+
148
+ Within your `Rakefile` add the following:
149
+
150
+ require 'xcoder/rake_task'
151
+
152
+ Then define your Rake Task:
153
+
154
+ Xcode::RakeTask.new
155
+
156
+ By default this will generate rake tasks within the 'xcode' namespace for
157
+ all the projects (within the current working directory), all their targets,
158
+ and all their configs. This will also generate tasks for all of a projects
159
+ schemes as well.
160
+
161
+ All names from the project, schemes, targets, and configs are remove
162
+ the camel-casing and replacing the cameling with underscores. Spaces
163
+ are replaced with dashes (-)
164
+
165
+ This will generate rake tasks that appear similar to the following:
166
+
167
+ rake xcode:project-name:targetname:debug:build
168
+ rake xcode:project-name:targetname:debug:clean
169
+ # ...
170
+
171
+ You can specify a parameter to change the root rake namespace:
172
+
173
+ Xcode::RakeTask.new :apple
174
+
175
+ # Resulting Rake Tasks:
176
+ # rake apple:project-name:targetname:debug:build
177
+ # rake apple:project-name:targetname:debug:clean
178
+ # ...
179
+
180
+ You can also supply a block to provide additional configuration to specify the folder to search for projects and the projects that should generate tasks for:
181
+
182
+ Xcode::RakeTask.new :hudson do |xcoder|
183
+ xcoder.directory = "projects"
184
+ xcoder.projects = [ "Project Alpha", "Project Beta" ]
185
+ end
186
+
187
+ rake hudson:project-alpha:targetname:debug:build
188
+ # ...
189
+
137
190
 
138
191
  ## Manipulating a Project
139
192
 
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ namespace :doc do
16
16
  desc "Generate YARD docs"
17
17
  YARD::Rake::YardocTask.new(:generate) do |t|
18
18
  t.files = ['lib/**/*.rb', '-', 'README.md'] # optional
19
- t.options = ["-o ../xcoder-doc"]
19
+ # t.options = ["-o ../xcoder-doc"]
20
20
  end
21
21
  end
22
22
 
@@ -27,7 +27,7 @@ namespace :test_project do
27
27
  task :reset do
28
28
  puts "Reseting the TestProject Project File"
29
29
  system "git co -- spec/TestProject"
30
- puts "Removing any User schemes generated for in the project"
30
+ puts "Removing any User schemes generated in the project"
31
31
  system "rm -rf spec/TestProject/TestProject.xcodeproj/xcuserdata"
32
32
  puts "Removing any installed files"
33
33
  system "git clean -df spec/TestProject"
@@ -35,3 +35,9 @@ namespace :test_project do
35
35
 
36
36
  end
37
37
 
38
+
39
+ require './lib/xcoder/rake_task'
40
+
41
+ Xcode::RakeTask.new :xcode do |xcoder|
42
+ xcoder.directory = 'spec'
43
+ end
@@ -54,7 +54,7 @@ module Xcode
54
54
  puts "[#{label}] Set build profile to #{@values[:profile]}"
55
55
  end
56
56
 
57
- Keychain.temp_keychain(@values[:project]) do |kc|
57
+ Keychain.temp do |kc|
58
58
  kc.import @values[:certificate], @values[:password]
59
59
 
60
60
  builder.identity = @values[:identity] || kc.identities.first
@@ -4,6 +4,7 @@ require 'xcode/configurations/targeted_device_family_property'
4
4
  require 'xcode/configurations/string_property'
5
5
  require 'xcode/configurations/boolean_property'
6
6
  require 'xcode/configurations/array_property'
7
+ require 'xcode/configurations/key_value_array_property'
7
8
 
8
9
  module Xcode
9
10
 
@@ -84,7 +85,18 @@ module Xcode
84
85
  # Define a getter method
85
86
 
86
87
  define_method property_name do
87
- substitute type.open(build_settings[setting_name])
88
+
89
+ # When the build setting is missing from the existing configuration, look
90
+ # for the configuration of the target's project (only if we are currently
91
+ # at the Target level).
92
+
93
+ if not build_settings.key?(setting_name) and target.is_a?(Target)
94
+ project_config = target.project.global_config(name)
95
+ project_config.send(property_name)
96
+ else
97
+ substitute type.open(build_settings[setting_name])
98
+ end
99
+
88
100
  end
89
101
 
90
102
  # Define a setter method
@@ -92,7 +104,39 @@ module Xcode
92
104
  define_method "#{property_name}=" do |value|
93
105
  build_settings[setting_name] = unsubstitute(type.save(value))
94
106
  end
107
+
108
+ # Define an append method
109
+
110
+ define_method "append_to_#{property_name}" do |value|
111
+ build_settings[setting_name] = unsubstitute type.append(build_settings[setting_name],value)
112
+ end
113
+
114
+ # Define a environment name method (to return the settings name)
115
+
116
+ define_method "env_#{property_name}" do
117
+ setting_name
118
+ end
119
+
120
+ # Define a raw getter
121
+
122
+ define_method "raw_#{property_name}" do
123
+ build_settings[setting_name]
124
+ end
125
+
126
+ # Define a raw setter
127
+
128
+ define_method "raw_#{property_name}=" do |value|
129
+ build_settings[setting_name] = value
130
+ end
131
+
95
132
 
133
+ @setting_name_to_property = {} unless @setting_name_to_property
134
+ @setting_name_to_property[setting_name] = property_name
135
+
136
+ end
137
+
138
+ def self.setting_name_to_property(name)
139
+ @setting_name_to_property[name]
96
140
  end
97
141
 
98
142
  #
@@ -149,7 +193,7 @@ module Xcode
149
193
  # @attribute
150
194
  # Build Setting - "OTHER_CFLAGS"
151
195
  # @see https://developer.apple.com/library/mac/documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW17
152
- property :other_c_flags, "OTHER_CFLAGS", ArrayProperty
196
+ property :other_c_flags, "OTHER_CFLAGS", KeyValueArrayProperty
153
197
 
154
198
  # @attribute
155
199
  # Build Setting - "GCC_C_LANGUAGE_STANDARD"
@@ -202,6 +246,11 @@ module Xcode
202
246
  # @see https://developer.apple.com/library/mac/#documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW144
203
247
  property :copy_phase_strip, "COPY_PHASE_STRIP", BooleanProperty
204
248
 
249
+ # @attribute
250
+ # Build Setting - "OTHER_LDFLAGS"
251
+ # @see https://developer.apple.com/library/mac/#documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW76
252
+ property :other_linker_flags, "OTHER_LDFLAGS", SpaceDelimitedString
253
+
205
254
  #
206
255
  # Opens the info plist associated with the configuration and allows you to
207
256
  # edit the configuration.
@@ -236,7 +285,11 @@ module Xcode
236
285
  # @return [String,Array,Hash] the value stored for the specified configuration
237
286
  #
238
287
  def get(name)
239
- build_settings[name]
288
+ if Configuration.setting_name_to_property(name)
289
+ send Configuration.setting_name_to_property(name)
290
+ else
291
+ build_settings[name]
292
+ end
240
293
  end
241
294
 
242
295
  #
@@ -246,7 +299,27 @@ module Xcode
246
299
  # @param [String,Array,Hash] value the value to store for the specific setting
247
300
  #
248
301
  def set(name, value)
249
- build_settings[name] = value
302
+ if Configuration.setting_name_to_property(name)
303
+ send("#{Configuration.setting_name_to_property(name)}=",value)
304
+ else
305
+ build_settings[name] = value
306
+ end
307
+ end
308
+
309
+ #
310
+ # Append a value to the the configuration value for the given name
311
+ #
312
+ # @param [String] name of the the configuration setting
313
+ # @param [String,Array,Hash] value the value to store for the specific setting
314
+ #
315
+ def append(name, value)
316
+ if Configuration.setting_name_to_property(name)
317
+ send("append_to_#{Configuration.setting_name_to_property(name)}",value)
318
+ else
319
+ # @todo this will likely raise some errors if trying to append a string
320
+ # to an array, but that likely means a new property should be defined.
321
+ build_settings[name] = build_settings[name] + value
322
+ end
250
323
  end
251
324
 
252
325
  #
@@ -0,0 +1,86 @@
1
+ module Xcode
2
+ module ConfigurationOwner
3
+
4
+ #
5
+ # @return [Array<BuildConfiguration>] the configurations that this target
6
+ # or project supports. These are generally 'Debug' or 'Release' but may be
7
+ # custom created configurations.
8
+ #
9
+ def configs
10
+ build_configuration_list.build_configurations.map do |config|
11
+ config.target = self
12
+ config
13
+ end
14
+ end
15
+
16
+ #
17
+ # Return a specific build configuration.
18
+ #
19
+ # @note an exception is raised if no configuration matches the specified name.
20
+ #
21
+ # @param [String] name of a configuration to return
22
+ #
23
+ # @return [BuildConfiguration] a specific build configuration that
24
+ # matches the specified name.
25
+ #
26
+ def config(name)
27
+ config = configs.select {|config| config.name == name.to_s }.first
28
+ raise "No such config #{name}, available configs are #{configs.map {|c| c.name}.join(', ')}" if config.nil?
29
+ yield config if block_given?
30
+ config
31
+ end
32
+
33
+ #
34
+ # Create a configuration for the target or project.
35
+ #
36
+ # @example creating a new 'App Store Submission' configuration for a project
37
+ #
38
+ # project.create_config 'App Store Submission' # => Configuration
39
+ #
40
+ # @example creating a new 'Ad Hoc' configuration for a target
41
+ #
42
+ # target.create_config 'Ad Hoc' do |config|
43
+ # # configuration the new debug config.
44
+ # end
45
+ #
46
+ # @param [String] name of the configuration to create
47
+ # @return [BuildConfiguration] that is created
48
+ #
49
+ def create_configuration(name)
50
+ # To create a configuration, we need to create or retrieve the configuration list
51
+
52
+ created_config = build_configuration_list.create_config(name) do |config|
53
+ yield config if block_given?
54
+ end
55
+
56
+ created_config
57
+ end
58
+
59
+ #
60
+ # Create multiple configurations for a target or project.
61
+ #
62
+ # @example creating 'Release' and 'Debug for a new target
63
+ #
64
+ # new_target = project.create_target 'UniversalBinary'
65
+ # new_target.create_configurations 'Debug', 'Release' do |config|
66
+ # # set up the configurations
67
+ # end
68
+ #
69
+ # @param [String,Array<String>] configuration_names the names of the
70
+ # configurations to create.
71
+ #
72
+ def create_configurations(*configuration_names)
73
+
74
+ configuration_names.compact.flatten.map do |config_name|
75
+ created_config = create_configuration config_name do |config|
76
+ yield config if block_given?
77
+ end
78
+
79
+ created_config.save!
80
+ end
81
+
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -16,7 +16,7 @@ module Xcode
16
16
  # @param [Array] value to be parsed into the correct format
17
17
  #
18
18
  def open(value)
19
- value.to_a
19
+ Array(value)
20
20
  end
21
21
 
22
22
  #
@@ -24,7 +24,11 @@ module Xcode
24
24
  # be in a multitude of formats as long as it responds_to? #to_a
25
25
  #
26
26
  def save(value)
27
- value.to_s
27
+ Array(value)
28
+ end
29
+
30
+ def append(original,value)
31
+ (Array(original) + Array(value)).uniq
28
32
  end
29
33
 
30
34
  end
@@ -35,6 +35,10 @@ module Xcode
35
35
  def save(value)
36
36
  value.to_s =~ /^(?:NO|false)$/ ? "NO" : "YES"
37
37
  end
38
+
39
+ def append(original,value)
40
+ save(original) | save(value)
41
+ end
38
42
 
39
43
  end
40
44
 
@@ -0,0 +1,42 @@
1
+
2
+ module Xcode
3
+ module Configuration
4
+
5
+ #
6
+ # Within the a build settings for a configuration there are a number of
7
+ # settings that are stored as key-value pairs in Arrays.
8
+ #
9
+ module KeyValueArrayProperty
10
+ extend self
11
+
12
+ #
13
+ # As arrays are stored as arrays this is not particularly different.
14
+ #
15
+ # @param [Array] value to be parsed into the correct format
16
+ #
17
+ def open(value)
18
+ Array(value)
19
+ end
20
+
21
+ #
22
+ # @param [Nil,Array,String] value that is being saved back which can
23
+ # be in a multitude of formats as long as it responds_to? #to_a
24
+ #
25
+ def save(value)
26
+ Array(value)
27
+ end
28
+
29
+ #
30
+ # To ensure uniqueness, the original value array is added to the new value
31
+ # array and then all the key-values pairs are placed in a Hash then mapped
32
+ # back out to a key=value pair array.
33
+ #
34
+ def append(original,value)
35
+ all_values = (Array(original) + Array(value)).map {|key_value| key_value.split("=") }.flatten
36
+ Hash[*all_values].map {|k,v| "#{k}=#{v}" }
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
@@ -40,6 +40,10 @@ module Xcode
40
40
  def save(value)
41
41
  Array(value).join(" ")
42
42
  end
43
+
44
+ def append(original,value)
45
+ save( ( open(original) + open(value) ).uniq )
46
+ end
43
47
 
44
48
  end
45
49
 
@@ -18,6 +18,10 @@ module Xcode
18
18
  def save(value)
19
19
  value.to_s
20
20
  end
21
+
22
+ def append(original,value)
23
+ original.to_s + value.to_s
24
+ end
21
25
 
22
26
  end
23
27
 
@@ -23,16 +23,26 @@ module Xcode
23
23
  end
24
24
 
25
25
  #
26
- # @param [Array<String>] value convert the array of platform names
26
+ # @param [String,Array<String>] value convert the array of platform names
27
27
  # @return [String] the comma-delimited list of numeric values representing
28
28
  # the platforms.
29
29
  #
30
30
  def save(value)
31
31
  Array(value).map do |platform_name|
32
32
  platforms.map {|number,name| number if name.to_s == platform_name.to_s.downcase }
33
- end.flatten.compact.join(",")
33
+ end.flatten.compact.uniq.join(",")
34
34
  end
35
-
35
+
36
+ #
37
+ # @param [String] original is the current string value stored in the configuration
38
+ # that needs to be converted into an Array of names.
39
+ # @param [String,Array<String>] value the new values to include in the device
40
+ # family.
41
+ #
42
+ def append(original,value)
43
+ save(open(original) + Array(value))
44
+ end
45
+
36
46
  private
37
47
 
38
48
  def platforms
data/lib/xcode/project.rb CHANGED
@@ -9,9 +9,9 @@ module Xcode
9
9
  # folder that contains a number of workspace files and project files like
10
10
  # `project.pbxproj`.
11
11
  #
12
- # The Project represents the top-most element within the structure. It contains
13
- # most of the methods that allow you to traverse through the targets and
14
- # configurations that it is composed.
12
+ #
13
+ # The Project represents encapsulates the an actual Resource that is ProjectReference
14
+ # and other objects.
15
15
  #
16
16
  class Project
17
17
 
@@ -78,6 +78,14 @@ module Xcode
78
78
  @registry.archive_version
79
79
  end
80
80
 
81
+ def global_configs
82
+ @project.configs
83
+ end
84
+
85
+ def global_config(name)
86
+ @project.config(name)
87
+ end
88
+
81
89
  #
82
90
  # Returns the main group of the project where all the files reside.
83
91
  #
@@ -115,31 +123,9 @@ module Xcode
115
123
  # @return [Group] the group with the specified name.
116
124
  #
117
125
  def group(name,options = {},&block)
118
- # By default create missing groups along the way
119
- options = { :create => true }.merge(options)
120
-
121
- current_group = @project.main_group
122
-
123
- # @todo consider this traversing and find/create as a normal procedure when
124
- # traversing the project.
125
-
126
- name.split("/").each do |path_component|
127
- found_group = current_group.group(path_component).first
128
-
129
- if options[:create] and found_group.nil?
130
- found_group = current_group.create_group(path_component)
131
- end
132
-
133
- current_group = found_group
134
-
135
- break unless current_group
136
- end
137
-
138
- current_group.instance_eval(&block) if block_given? and current_group
139
-
140
- current_group
126
+ @project.group(name,options,&block)
141
127
  end
142
-
128
+
143
129
  #
144
130
  # Return the file that matches the specified path. This will traverse
145
131
  # the project's groups and find the file at the end of the path.
@@ -159,9 +145,7 @@ module Xcode
159
145
  # @return [Group] the 'Products' group of the project.
160
146
  def products_group
161
147
  current_group = groups.group('Products').first
162
-
163
148
  current_group.instance_eval(&block) if block_given? and current_group
164
-
165
149
  current_group
166
150
  end
167
151
 
@@ -173,9 +157,7 @@ module Xcode
173
157
  # @return [Group] the 'Frameworks' group of the projet.
174
158
  def frameworks_group
175
159
  current_group = groups.group('Frameworks').first
176
-
177
160
  current_group.instance_eval(&block) if block_given? and current_group
178
-
179
161
  current_group
180
162
  end
181
163
 
@@ -0,0 +1,55 @@
1
+ module Xcode
2
+
3
+ module ProjectReference
4
+
5
+ #
6
+ # Returns the group specified. If any part of the group does not exist along
7
+ # the path the group is created. Also paths can be specified to make the
8
+ # traversing of the groups easier.
9
+ #
10
+ # @note this will attempt to find the paths specified, if it fails to find them
11
+ # it will create one and then continue traversing.
12
+ #
13
+ # @example Traverse a path through the various sub-groups.
14
+ #
15
+ # project.group('Vendor/MyCode/Support Files')
16
+ # # is equivalent to ...
17
+ # project.group('Vendor').first.group('MyCode').first.group('Supporting Files')
18
+ #
19
+ # @note this path functionality current is only exercised from the project level
20
+ # all groups will treat the path division `/` as simply a character.
21
+ #
22
+ # @param [String] name the group name to find/create
23
+ #
24
+ # @return [Group] the group with the specified name.
25
+ #
26
+ def group(name,options = {},&block)
27
+ # By default create missing groups along the way
28
+ options = { :create => true }.merge(options)
29
+
30
+ current_group = main_group
31
+
32
+ # @todo consider this traversing and find/create as a normal procedure when
33
+ # traversing the project.
34
+
35
+ name.split("/").each do |path_component|
36
+ found_group = current_group.group(path_component).first
37
+
38
+ if options[:create] and found_group.nil?
39
+ found_group = current_group.create_group(path_component)
40
+ end
41
+
42
+ current_group = found_group
43
+
44
+ break unless current_group
45
+ end
46
+
47
+ current_group.instance_eval(&block) if block_given? and current_group
48
+
49
+ current_group
50
+ end
51
+
52
+
53
+
54
+ end
55
+ end
@@ -1,17 +1,31 @@
1
1
  module Xcode
2
2
  class ProvisioningProfile
3
- attr_reader :path, :name, :uuid, :identifiers
3
+ attr_reader :path, :name, :uuid, :identifiers, :devices, :appstore
4
4
  def initialize(path)
5
5
 
6
6
  raise "Provisioning profile '#{path}' does not exist" unless File.exists? path
7
7
 
8
8
  @path = path
9
9
  @identifiers = []
10
+ @devices = []
11
+ @appstore = true
10
12
 
11
13
  # TODO: im sure this could be done in a nicer way. maybe read out the XML-like stuff and use the plist -> json converter
12
14
  uuid = nil
13
15
  File.open(path, "rb") do |f|
14
16
  input = f.read
17
+
18
+ if input=~/ProvisionedDevices/
19
+ @appstore = false
20
+ end
21
+
22
+ if input=~/<key>ProvisionedDevices<\/key>.*?<array>(.*?)<\/array>/im
23
+ $1.split(/<string>/).each do |id|
24
+ next if id.nil? or id.strip==""
25
+ @devices << id.gsub(/<\/string>/,'').strip
26
+ end
27
+ end
28
+
15
29
  input=~/<key>UUID<\/key>.*?<string>(.*?)<\/string>/im
16
30
  @uuid = $1.strip
17
31
 
@@ -26,6 +40,10 @@ module Xcode
26
40
  end
27
41
 
28
42
  end
43
+
44
+ def appstore?
45
+ @appstore
46
+ end
29
47
 
30
48
  def self.profiles_path
31
49
  File.expand_path "~/Library/MobileDevice/Provisioning\\ Profiles/"