xcoder 0.1.6 → 0.1.7

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 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/"