vendor 0.0.4 → 0.1

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.
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