vendor 0.0.4 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/CHANGELOG.md +22 -0
  2. data/Gemfile.lock +9 -1
  3. data/Guardfile +12 -0
  4. data/LICENSE +2 -0
  5. data/Readme.markdown +39 -23
  6. data/TODO.md +26 -0
  7. data/VERSION +1 -0
  8. data/lib/vendor.rb +6 -0
  9. data/lib/vendor/api.rb +61 -7
  10. data/lib/vendor/cli/app.rb +4 -4
  11. data/lib/vendor/cli/console.rb +7 -0
  12. data/lib/vendor/spec.rb +98 -0
  13. data/lib/vendor/templates/Vendorfile +3 -1
  14. data/lib/vendor/templates/vendorspec +15 -10
  15. data/lib/vendor/vendor_file.rb +5 -4
  16. data/lib/vendor/vendor_file/dependency_graph.rb +135 -0
  17. data/lib/vendor/vendor_file/dsl.rb +2 -0
  18. data/lib/vendor/vendor_file/library/base.rb +178 -29
  19. data/lib/vendor/vendor_file/library/git.rb +5 -1
  20. data/lib/vendor/vendor_file/library/local.rb +11 -1
  21. data/lib/vendor/vendor_file/library/remote.rb +134 -2
  22. data/lib/vendor/vendor_file/loader.rb +13 -11
  23. data/lib/vendor/vendor_spec/builder.rb +4 -7
  24. data/lib/vendor/version.rb +172 -1
  25. data/lib/vendor/xcode/project.rb +213 -4
  26. data/lib/vendor/xcode/proxy.rb +1 -0
  27. data/lib/vendor/xcode/proxy/pbx_frameworks_build_phase.rb +6 -0
  28. data/lib/vendor/xcode/proxy/pbx_reference_proxy.rb +7 -0
  29. data/lib/vendor/xcode/proxy/pbx_resources_build_phase.rb +8 -0
  30. data/lib/vendor/xcode/proxy/pbx_shell_script_build_phase.rb +8 -0
  31. data/lib/vendor/xcode/proxy/pbx_sources_build_phase.rb +6 -0
  32. data/spec/lib/vendor/api_spec.rb +54 -0
  33. data/spec/lib/vendor/spec_spec.rb +121 -0
  34. data/spec/lib/vendor/vendor_file/dependency_graph_spec.rb +129 -0
  35. data/spec/lib/vendor/vendor_file/library/base_spec.rb +174 -14
  36. data/spec/lib/vendor/vendor_file/library/remote_spec.rb +154 -4
  37. data/spec/lib/vendor/vendor_file/loader_spec.rb +4 -2
  38. data/spec/lib/vendor/vendor_spec/builder_spec.rb +2 -2
  39. data/spec/lib/vendor/version_spec.rb +168 -0
  40. data/spec/lib/vendor/xcode/project_spec.rb +175 -4
  41. data/spec/lib/vendor_spec.rb +15 -0
  42. data/spec/spec_helper.rb +3 -2
  43. data/spec/support/api_stubs.rb +57 -0
  44. data/spec/support/resources/cache/base/{DKBenchmark-Manifest → DKBenchmark-0.1-Manifest}/data/DKBenchmark.h +0 -0
  45. data/spec/support/resources/cache/base/{DKBenchmark-Manifest → DKBenchmark-0.1-Manifest}/data/DKBenchmark.m +0 -0
  46. data/spec/support/resources/cache/base/DKBenchmark-0.1-Manifest/vendor.json +1 -0
  47. data/spec/support/resources/cache/base/{DKBenchmark-Vendorspec → DKBenchmark-0.1-Nothing}/DKBenchmark.h +0 -0
  48. data/spec/support/resources/cache/base/{DKBenchmark-Vendorspec → DKBenchmark-0.1-Nothing}/DKBenchmark.m +0 -0
  49. data/spec/support/resources/cache/base/DKBenchmark-0.1-Nothing/DKBenchmark.vendorspec +16 -0
  50. data/spec/support/resources/cache/base/DKBenchmark-0.1-Vendorspec/DKBenchmark.h +18 -0
  51. data/spec/support/resources/cache/base/DKBenchmark-0.1-Vendorspec/DKBenchmark.m +73 -0
  52. data/spec/support/resources/cache/base/DKBenchmark-0.1-Vendorspec/DKBenchmark.vendorspec +24 -0
  53. data/spec/support/resources/projects/MultipleTargets/MultipleTargets.xcodeproj/project.pbxproj +624 -0
  54. data/spec/support/resources/projects/RestKitProject/RestKitProject.xcodeproj/project.pbxproj +479 -0
  55. data/spec/support/resources/projects/UtilityApplication/UtilityApplication.xcodeproj/project.pbxproj +16 -7
  56. data/spec/support/resources/vendors/DKBenchmark/DKBenchmark.vendorspec +24 -8
  57. data/spec/support/resources/vendors/DKBenchmarkUnsafe/DKBenchmark.vendorspec +17 -8
  58. data/vendor.gemspec +4 -2
  59. metadata +93 -39
  60. data/lib/vendor/vendor_spec/dsl.rb +0 -39
  61. data/lib/vendor/vendor_spec/loader.rb +0 -23
  62. data/spec/lib/vendor/vendor_spec/dsl_spec.rb +0 -67
  63. data/spec/lib/vendor/vendor_spec/loader_spec.rb +0 -41
  64. data/spec/support/resources/cache/base/DKBenchmark-Manifest/vendor.json +0 -1
  65. data/spec/support/resources/cache/base/DKBenchmark-Vendorspec/DKBenchmark.vendorspec +0 -11
@@ -44,7 +44,11 @@ module Vendor
44
44
  # the sources correctly. So instead of using the name, use a hash of
45
45
  # the URI
46
46
  def cache_path
47
- @cache_path ||= File.join(Vendor.library_path, "git", Digest::MD5.hexdigest(uri))
47
+ File.join(Vendor.library_path, "git", Digest::MD5.hexdigest(uri)) if uri
48
+ end
49
+
50
+ def display_name
51
+ [ name, "(#{uri}##{tag})" ].join(' ')
48
52
  end
49
53
 
50
54
  private
@@ -12,9 +12,19 @@ module Vendor
12
12
  end
13
13
 
14
14
  def cache_path
15
- File.expand_path(path)
15
+ expanded_path if path
16
16
  end
17
17
 
18
+ def display_name
19
+ [ name, "(#{expanded_path})" ].join(' ')
20
+ end
21
+
22
+ private
23
+
24
+ def expanded_path
25
+ File.expand_path(path)
26
+ end
27
+
18
28
  end
19
29
 
20
30
  end
@@ -4,13 +4,145 @@ module Vendor
4
4
 
5
5
  class Remote < Base
6
6
 
7
- attr_accessor :version
7
+ require 'zip/zip'
8
+
9
+ attr_accessor :equality
8
10
  attr_accessor :sources
9
11
 
12
+ def version
13
+ @version
14
+ end
15
+
16
+ def version=(value)
17
+ # Matches (some equality matcher, followed by some spaces, then a version)
18
+ if value
19
+ if value.match(/^(\<\=|\>\=|\~\>|\>|\<)?\s*([a-zA-Z0-9\.]+)$/)
20
+ @equality = $1
21
+ @version = $2
22
+ else
23
+ Vendor.ui.error "Invalid version format '#{value}' for '#{name}'"
24
+ exit 1
25
+ end
26
+ else
27
+ @equality = nil
28
+ @version = nil
29
+ end
30
+ value
31
+ end
32
+
33
+ def matched_version
34
+ # If we just have a version, and no equality matcher
35
+ if version && !equality
36
+ return version
37
+ end
38
+
39
+ # If we don't have a version, get the latest version
40
+ # from the remote sources
41
+ unless version
42
+ return meta['release']
43
+ end
44
+
45
+ # Try and find a version that matches the lib
46
+ other_versions = meta['versions'].map { |v| v[0] }
47
+ found = version_matches_any?(other_versions)
48
+
49
+ # Did we actually find something?
50
+ unless found
51
+ Vendor.ui.error "Could not find a valid version '#{equality} #{version}' in '#{other_versions}'"
52
+ exit 1
53
+ else
54
+ found
55
+ end
56
+ end
57
+
58
+ def version_matches_any?(other_versions)
59
+ # If we have an equality matcher, we need sort through
60
+ # the versions, and try and find the best match
61
+ wants = Vendor::Version.new(version)
62
+
63
+ # Sort them from the latest versions first
64
+ versions = other_versions.map{|v| Vendor::Version.create(v) }.sort.reverse
65
+
66
+ # We don't want to include pre-releases if the wants version
67
+ # isn't a pre-release itself. If we're after "2.5.alpha", then
68
+ # we should be able to include that, however if we're asking for
69
+ # "2.5", then pre-releases shouldn't be included.
70
+ unless wants.prerelease?
71
+ versions = versions.reject { |v| v.prerelease? }
72
+ end
73
+
74
+ # If this a spermy search, we have a slightly different search mechanism. We use the
75
+ # version segemets to determine if the version is valid. For example, lets say we're
76
+ # testing to see if "0.1" is a spermy match to "0.1.5.2", we start by getting the
77
+ # two segments for each:
78
+ #
79
+ # [ 0, 1 ]
80
+ # [ 0, 1, 5, 2 ]
81
+ #
82
+ # We chop the second set of segments to be the same lenth as the first one:
83
+ #
84
+ # [ 0, 1, 5, 2 ].slice(0, 2) #=. [ 0, 1 ]
85
+ #
86
+ # No we just test to see if they are the same! I think this works...
87
+ if equality == "~>"
88
+ segments = wants.segments
89
+ versions.find do |has|
90
+ segments == has.segments.slice(0, segments.length)
91
+ end
92
+ elsif equality
93
+ versions.find do |has|
94
+ wants.send(:"#{equality}", has)
95
+ end
96
+ else
97
+ versions.find do |has|
98
+ wants == has
99
+ end
100
+ end
101
+ end
102
+
10
103
  def download
11
- Vendor.ui.info "download #{name} version #{version} from #{sources} to #{cache_path}"
104
+ # If we haven't already downloaded the vendor
105
+ unless File.exist?(cache_path)
106
+ # Download it
107
+ file = Vendor::API.download(name, matched_version)
108
+
109
+ # Unzip the download
110
+ unzip_file file.path, cache_path
111
+ end
112
+ end
113
+
114
+ def cache_path
115
+ File.join(Vendor.library_path, "remote", name, matched_version.to_s)
12
116
  end
13
117
 
118
+ def ==(other)
119
+ other.name == @name && other.version == @version && other.equality == @equality
120
+ end
121
+
122
+ def display_name
123
+ [ name, matched_version ].compact.join(" ")
124
+ end
125
+
126
+ def description
127
+ [ @name, @equality, @version ].compact.join(" ")
128
+ end
129
+
130
+ private
131
+
132
+ def meta
133
+ @meta ||= Vendor::API.meta(name)
134
+ end
135
+
136
+ def unzip_file (file, destination)
137
+ Zip::ZipFile.open(file) { |zip_file|
138
+ zip_file.each { |f|
139
+ f_path = File.join(destination, f.name)
140
+ FileUtils.mkdir_p(File.dirname(f_path))
141
+ zip_file.extract(f, f_path) unless File.exist?(f_path)
142
+ }
143
+ }
144
+ end
145
+
14
146
  end
15
147
 
16
148
  end
@@ -6,27 +6,29 @@ module Vendor
6
6
  require 'fileutils'
7
7
 
8
8
  attr_reader :dsl
9
+ attr_reader :libraries
9
10
 
10
11
  def initialize
11
12
  @dsl = Vendor::VendorFile::DSL.new
13
+ @libraries = []
12
14
  end
13
15
 
14
- def load(filename)
15
- @dsl.instance_eval(File.read(filename), filename)
16
+ def libraries=(value)
17
+ @graph = Vendor::VendorFile::DependencyGraph.new(value)
18
+ @libraries = value
16
19
  end
17
20
 
18
- def download
19
- @dsl.libraries.each do |lib|
20
- if lib.respond_to?(:sources=)
21
- lib.sources = @dsl.sources
22
- end
23
- lib.download
24
- end
21
+ def load(filename)
22
+ @dsl.instance_eval(File.read(filename), filename)
23
+ self.libraries = @dsl.libraries
25
24
  end
26
25
 
27
26
  def install(project)
28
- @dsl.libraries.each do |lib|
29
- lib.install project
27
+ unless @graph.version_conflicts?
28
+ @graph.libraries_to_install.each do |lib|
29
+ library, targets = lib
30
+ library.install project, :targets => targets
31
+ end
30
32
  end
31
33
  end
32
34
 
@@ -15,14 +15,11 @@ module Vendor
15
15
  attr_reader :vendor_spec
16
16
 
17
17
  def initialize(vendor_spec)
18
- loader = Vendor::VendorSpec::Loader.new
19
- loader.load vendor_spec
20
-
21
18
  @folder = File.expand_path(File.join(vendor_spec, '..'))
22
- @vendor_spec = loader.dsl.vendor_spec
19
+ @vendor_spec = Vendor::Spec.load vendor_spec
23
20
 
24
- @name = safe_filename(@vendor_spec[:name])
25
- @version = safe_filename(@vendor_spec[:version])
21
+ @name = safe_filename(@vendor_spec.name)
22
+ @version = safe_filename(@vendor_spec.version)
26
23
  @filename = "#{@name}-#{@version}.vendor"
27
24
  end
28
25
 
@@ -49,7 +46,7 @@ module Vendor
49
46
 
50
47
  # Remove files that are within folders with a ".", such as ".bundle"
51
48
  # and ".frameworks"
52
- copy_files = @vendor_spec[:files].reject { |file| file =~ /\/?[^\/]+\.[^\/]+\// }
49
+ copy_files = @vendor_spec.files.reject { |file| file =~ /\/?[^\/]+\.[^\/]+\// }
53
50
 
54
51
  copy_files.each do |file|
55
52
  dir = File.dirname(file)
@@ -1,5 +1,176 @@
1
1
  module Vendor
2
2
 
3
- VERSION = "0.0.4"
3
+ class Version
4
+
5
+ include Comparable
6
+
7
+ VERSION_PATTERN = '[0-9]+(\.[0-9a-zA-Z]+)*' # :nodoc:
8
+ ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})*\s*\z/ # :nodoc:
9
+
10
+ ##
11
+ # A string representation of this Version.
12
+
13
+ attr_reader :version
14
+ alias to_s version
15
+
16
+ ##
17
+ # True if the +version+ string matches Vendor's requirements.
18
+
19
+ def self.correct? version
20
+ version.to_s =~ ANCHORED_VERSION_PATTERN
21
+ end
22
+
23
+ ##
24
+ # Factory method to create a Version object. Input may be a Version
25
+ # or a String. Intended to simplify client code.
26
+ #
27
+ # ver1 = Version.create('1.3.17') # -> (Version object)
28
+ # ver2 = Version.create(ver1) # -> (ver1)
29
+ # ver3 = Version.create(nil) # -> nil
30
+
31
+ def self.create input
32
+ if input === Vendor::Version
33
+ input
34
+ elsif input.nil? then
35
+ nil
36
+ else
37
+ new input
38
+ end
39
+ end
40
+
41
+ ##
42
+ # Constructs a Version from the +version+ string. A version string is a
43
+ # series of digits or ASCII letters separated by dots.
44
+
45
+ def initialize version
46
+ raise ArgumentError, "Malformed version number string #{version}" unless
47
+ self.class.correct?(version)
48
+
49
+ @version = version.to_s
50
+ @version.strip!
51
+ end
52
+
53
+ ##
54
+ # Return a new version object where the next to the last revision
55
+ # number is one greater (e.g., 5.3.1 => 5.4).
56
+ #
57
+ # Pre-release (alpha) parts, e.g, 5.3.1.b.2 => 5.4, are ignored.
58
+
59
+ def bump
60
+ segments = self.segments.dup
61
+ segments.pop while segments.any? { |s| String === s }
62
+ segments.pop if segments.size > 1
63
+
64
+ segments[-1] = segments[-1].succ
65
+ self.class.new segments.join(".")
66
+ end
67
+
68
+ ##
69
+ # A Version is only eql? to another version if it's specified to the
70
+ # same precision. Version "1.0" is not the same as version "1".
71
+
72
+ def eql? other
73
+ self.class === other and @version == other.version
74
+ end
75
+
76
+ def inspect # :nodoc:
77
+ "#<#{self.class} #{version.inspect}>"
78
+ end
79
+
80
+ ##
81
+ # Dump only the raw version string, not the complete object. It's a
82
+ # string for backwards (RubyGems 1.3.5 and earlier) compatibility.
83
+
84
+ def marshal_dump
85
+ [version]
86
+ end
87
+
88
+ ##
89
+ # Load custom marshal format. It's a string for backwards (RubyGems
90
+ # 1.3.5 and earlier) compatibility.
91
+
92
+ def marshal_load array
93
+ initialize array[0]
94
+ end
95
+
96
+ ##
97
+ # A version is considered a prerelease if it contains a letter.
98
+
99
+ def prerelease?
100
+ @prerelease ||= @version =~ /[a-zA-Z]/
101
+ end
102
+
103
+ def pretty_print q # :nodoc:
104
+ q.text "Vendor::Version.new(#{version.inspect})"
105
+ end
106
+
107
+ ##
108
+ # The release for this version (e.g. 1.2.0.a -> 1.2.0).
109
+ # Non-prerelease versions return themselves.
110
+
111
+ def release
112
+ return self unless prerelease?
113
+
114
+ segments = self.segments.dup
115
+ segments.pop while segments.any? { |s| String === s }
116
+ self.class.new segments.join('.')
117
+ end
118
+
119
+ def segments # :nodoc:
120
+
121
+ # segments is lazy so it can pick up version values that come from
122
+ # old marshaled versions, which don't go through marshal_load.
123
+
124
+ @segments ||= @version.scan(/[0-9]+|[a-z]+/i).map do |s|
125
+ /^\d+$/ =~ s ? s.to_i : s
126
+ end
127
+ end
128
+
129
+ ##
130
+ # A recommended version for use with a ~> Requirement.
131
+
132
+ def spermy_recommendation
133
+ segments = self.segments.dup
134
+
135
+ segments.pop while segments.any? { |s| String === s }
136
+ segments.pop while segments.size > 2
137
+ segments.push 0 while segments.size < 2
138
+
139
+ "~> #{segments.join(".")}"
140
+ end
141
+
142
+ ##
143
+ # Compares this version with +other+ returning -1, 0, or 1 if the
144
+ # other version is larger, the same, or smaller than this
145
+ # one. Attempts to compare to something that's not a
146
+ # <tt>Vendor::Version</tt> return +nil+.
147
+
148
+ def <=> other
149
+ return unless Vendor::Version === other
150
+ return 0 if @version == other.version
151
+
152
+ lhsegments = segments
153
+ rhsegments = other.segments
154
+
155
+ lhsize = lhsegments.size
156
+ rhsize = rhsegments.size
157
+ limit = (lhsize > rhsize ? lhsize : rhsize) - 1
158
+
159
+ i = 0
160
+
161
+ while i <= limit
162
+ lhs, rhs = lhsegments[i] || 0, rhsegments[i] || 0
163
+ i += 1
164
+
165
+ next if lhs == rhs
166
+ return -1 if String === lhs && Numeric === rhs
167
+ return 1 if Numeric === lhs && String === rhs
168
+
169
+ return lhs <=> rhs
170
+ end
171
+
172
+ return 0
173
+ end
174
+ end
4
175
 
5
176
  end
@@ -4,6 +4,11 @@ module Vendor::XCode
4
4
 
5
5
  class Project
6
6
 
7
+ # Build settings and the format that they should be stored in
8
+ BUILD_SETTING_TYPES = {
9
+ "OTHER_LDFLAGS" => :array
10
+ }
11
+
7
12
  require 'fileutils'
8
13
 
9
14
  attr_reader :name
@@ -55,8 +60,13 @@ module Vendor::XCode
55
60
  @objects_by_id[id]
56
61
  end
57
62
 
58
- def find_target(name)
59
- root_object.targets.find { |x| x.name == name }
63
+ def find_target(t)
64
+ # Are we already a target?
65
+ if t.kind_of?(Vendor::XCode::Proxy::Base)
66
+ t
67
+ else
68
+ root_object.targets.find { |x| x.name == t }
69
+ end
60
70
  end
61
71
 
62
72
  def find_group(path)
@@ -105,6 +115,8 @@ module Vendor::XCode
105
115
  # If we have the group
106
116
  if group
107
117
 
118
+ ids_to_remove = []
119
+
108
120
  # Remove the children from the file system
109
121
  group.children.each do |child|
110
122
  if child.group? # Is it a group?
@@ -120,13 +132,27 @@ module Vendor::XCode
120
132
  Vendor.ui.error "Couldn't remove object: #{child}"
121
133
  end
122
134
 
135
+ # Remove from the targets (if we can)
136
+ root_object.targets.each { |t| remove_file_from_target(child, t) }
137
+
123
138
  # Remove the file from the parent
124
139
  child.parent.attributes['children'].delete child.id
140
+
141
+ # Add the id to the list of stuff to remove. If we do this
142
+ # during the loop, bad things happen - not sure why.
143
+ ids_to_remove << child.id
125
144
  end
126
145
 
127
146
  # Remove the group from the parent
128
147
  group.parent.attributes['children'].delete group.id
129
148
 
149
+ # Add to the list of stuff to remove
150
+ ids_to_remove << group.id
151
+
152
+ ids_to_remove.each do |id|
153
+ @objects_by_id.delete id
154
+ end
155
+
130
156
  # Mark as dirty
131
157
  @dirty = true
132
158
 
@@ -135,11 +161,101 @@ module Vendor::XCode
135
161
  end
136
162
  end
137
163
 
164
+ def add_framework(framework, options = {})
165
+ # Find targets
166
+ targets = targets_from_options(options)
167
+ Vendor.ui.debug %{Adding #{framework} to targets "#{targets.map(&:name)}"}
168
+
169
+ targets.each do |t|
170
+ # Does the framework already exist?
171
+ build_phase = build_phase_for_file("wrapper.framework", t)
172
+
173
+ if build_phase
174
+ # Does a framework already exist?
175
+ existing_framework = build_phase.files.map(&:file_ref).find do |file|
176
+ # Some files have names, some done. Framework references
177
+ # have names...
178
+ if file.respond_to?(:name)
179
+ file.name == framework
180
+ end
181
+ end
182
+
183
+ # If an existing framework was found, don't add it again
184
+ unless existing_framework
185
+ add_file :targets => t, :file => "System/Library/Frameworks/#{framework}", :path => "Frameworks", :source_tree => :sdkroot
186
+ end
187
+ end
188
+ end
189
+ end
190
+
191
+ def add_build_setting(name, value, options = {})
192
+
193
+ targets_from_options(options).each do |target|
194
+
195
+ target.build_configuration_list.build_configurations.each do |config|
196
+
197
+ build_settings = config.build_settings
198
+
199
+ debug_key = "#{target.name}/#{config.name}".inspect
200
+
201
+ # If the build setting already has the key
202
+ if build_settings.has_key?(name)
203
+
204
+ # Is this setting known to have multiple values?
205
+ if (setting_type = BUILD_SETTING_TYPES[name])
206
+
207
+ # If its an array
208
+ if setting_type == :array
209
+ # Is it already an array, if so, check to see if
210
+ # it already exists, and if it doesn't add it.
211
+ if build_settings[name].kind_of?(Array)
212
+ unless build_settings[name].include?
213
+ Vendor.ui.debug(" Added build setting (#{name.inspect} = #{value.inspect}) to #{debug_key}")
214
+
215
+ build_settings[name] << value
216
+ end
217
+ else
218
+ Vendor.ui.debug(" Added build setting (#{name.inspect} = #{value.inspect}) to #{debug_key}")
219
+
220
+ # Don't add it if the single build value is already there
221
+ unless build_settings[name].strip == value.strip
222
+ build_settings[name] = [ build_settings[name], value ]
223
+ end
224
+ end
225
+ end
226
+
227
+ else
228
+ # If its an unknown type, then we should just throw a warning
229
+ # because we should'nt just change stuff willy, nilly.
230
+ Vendor.ui.warn(" Build setting #{name.inspect} wanted to change to #{value.inspect}, but it was already #{build_settings[name].inspect} in #{debug_key}")
231
+ end
232
+
233
+ else
234
+ Vendor.ui.debug(" Added build setting (#{name.inspect} = #{value.inspect}) to #{debug_key}")
235
+
236
+ build_settings[name] = value
237
+ end
238
+
239
+ end
240
+
241
+ end
242
+
243
+ @dirty = true
244
+
245
+ end
246
+
138
247
  def add_file(options)
139
248
  require_options options, :path, :file, :source_tree
140
249
 
141
- # Ensure file exists
142
- raise StandardError.new("Could not find file `#{options[:file]}`") unless File.exists?(options[:file])
250
+ # Ensure file exists if we'nre not using the sdk root source tree.
251
+ # The SDKROOT source tree has a virtual file on the system, so
252
+ # File.exist checks will always return false.
253
+ unless options[:source_tree] == :sdkroot
254
+ raise StandardError.new("Could not find file `#{options[:file]}`") unless File.exists?(options[:file])
255
+ end
256
+
257
+ # Find targets
258
+ targets = targets_from_options(options)
143
259
 
144
260
  # Create the group
145
261
  group = create_group(options[:path])
@@ -174,6 +290,12 @@ module Vendor::XCode
174
290
  attributes['name'] = name
175
291
  attributes['path'] = options[:file]
176
292
 
293
+ elsif options[:source_tree] == :sdkroot
294
+
295
+ # Set the path and the name of the framework
296
+ attributes['path'] = options[:file]
297
+ attributes['sourceTree'] = "SDKROOT"
298
+
177
299
  else
178
300
 
179
301
  # Could not handle that option
@@ -192,6 +314,11 @@ module Vendor::XCode
192
314
  # Add the file id to the groups children
193
315
  group.attributes['children'] << file.id
194
316
 
317
+ # Add the file to targets
318
+ targets.each do |t|
319
+ add_file_to_target file, t
320
+ end
321
+
195
322
  # Mark as dirty
196
323
  @dirty = true
197
324
 
@@ -199,6 +326,50 @@ module Vendor::XCode
199
326
  @objects_by_id[file.id] = file
200
327
  end
201
328
 
329
+ def remove_file_from_target(file, target)
330
+
331
+ # Search through all the build phases for references to the file
332
+ build_files = []
333
+ target.build_phases.each do |phase|
334
+ build_files << phase.files.find_all do |build_file|
335
+ build_file.attributes['fileRef'] == file.id
336
+ end
337
+ end
338
+
339
+ # Remove the build files from the references
340
+ build_files.flatten.each do |build_file|
341
+ build_file.parent.attributes['files'].delete build_file.id
342
+ @objects_by_id.delete build_file.id
343
+ end
344
+
345
+ end
346
+
347
+ def add_file_to_target(file, target)
348
+
349
+ build_phase = build_phase_for_file(file.last_known_file_type, target)
350
+
351
+ if build_phase
352
+
353
+ Vendor.ui.debug "Adding #{file.attributes} to #{target.name} (build_phase = #{build_phase.class.name})"
354
+
355
+ # Add the file to XCode
356
+ build_file = Vendor::XCode::Proxy::PBXBuildFile.new(:project => self,
357
+ :id => Vendor::XCode::Proxy::Base.generate_id,
358
+ :attributes => { 'fileRef' => file.id })
359
+
360
+ # Set the parent
361
+ build_file.parent = build_phase
362
+
363
+ # Add the file to the internal index
364
+ @objects_by_id[build_file.id] = build_file
365
+
366
+ # Add the file to the build phase
367
+ build_phase.attributes['files'] << build_file.id
368
+
369
+ end
370
+
371
+ end
372
+
202
373
  def to_ascii_plist
203
374
  plist = { :archiveVersion => archive_version,
204
375
  :classes => {},
@@ -240,10 +411,48 @@ module Vendor::XCode
240
411
 
241
412
  private
242
413
 
414
+ def build_phase_for_file(file_type, target)
415
+ # Which build phase does this file belong?
416
+ klass = case file_type
417
+ when "sourcecode.c.objc"
418
+ Vendor::XCode::Proxy::PBXSourcesBuildPhase
419
+ when "wrapper.framework"
420
+ Vendor::XCode::Proxy::PBXFrameworksBuildPhase
421
+ end
422
+
423
+ if klass
424
+ # Find the build phase
425
+ build_phase = target.build_phases.find { |phase| phase.kind_of?(klass) }
426
+ unless build_phase
427
+ Vendor.ui.error "Could not find '#{klass.name}' build phase for target '#{target.name}'"
428
+ exit 1
429
+ end
430
+ build_phase
431
+ else
432
+ false
433
+ end
434
+ end
435
+
243
436
  def require_options(options, *keys)
244
437
  keys.each { |k| raise StandardError.new("Missing :#{k} option") unless options[k] }
245
438
  end
246
439
 
440
+ def targets_from_options(options)
441
+ if options[:targets]
442
+ [ *options[:targets] ].map do |t|
443
+ if t == :all
444
+ root_object.targets
445
+ else
446
+ target = find_target(t)
447
+ raise StandardError.new("Could not find target '#{t}' in project '#{name}'") unless target
448
+ target
449
+ end
450
+ end.flatten.uniq
451
+ else
452
+ root_object.targets
453
+ end
454
+ end
455
+
247
456
  end
248
457
 
249
458
  end