xcodeproj 0.28.2 → 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -62,6 +62,22 @@ module Xcodeproj
62
62
  @xml_element.add_element(runnable.xml_element) if runnable
63
63
  end
64
64
 
65
+ # @return [EnvironmentVariables]
66
+ # Returns the EnvironmentVariables that will be defined at app launch
67
+ #
68
+ def environment_variables
69
+ EnvironmentVariables.new(@xml_element.elements[XCScheme::VARIABLES_NODE])
70
+ end
71
+
72
+ # @param [EnvironmentVariables,nil] env_vars
73
+ # Sets the EnvironmentVariables that will be defined at app launch
74
+ #
75
+ def environment_variables=(env_vars)
76
+ @xml_element.delete_element(XCScheme::VARIABLES_NODE)
77
+ @xml_element.add_element(env_vars.xml_element) if env_vars
78
+ env_vars
79
+ end
80
+
65
81
  # @todo handle 'AdditionalOptions' tag
66
82
  end
67
83
  end
@@ -1,4 +1,5 @@
1
1
  require 'xcodeproj/scheme/abstract_scheme_action'
2
+ require 'xcodeproj/scheme/environment_variables'
2
3
 
3
4
  module Xcodeproj
4
5
  class XCScheme
@@ -84,6 +85,23 @@ module Xcodeproj
84
85
  @xml_element.add_element(macro_expansion.xml_element)
85
86
  end
86
87
 
88
+ # @return [EnvironmentVariables]
89
+ # Returns the EnvironmentVariables that will be defined at test launch
90
+ #
91
+ def environment_variables
92
+ EnvironmentVariables.new(@xml_element.elements[XCScheme::VARIABLES_NODE])
93
+ end
94
+
95
+ # @param [EnvironmentVariables,nil] env_vars
96
+ # Sets the EnvironmentVariables that will be defined at test launch
97
+ # @return [EnvironmentVariables]
98
+ #
99
+ def environment_variables=(env_vars)
100
+ @xml_element.delete_element(XCScheme::VARIABLES_NODE)
101
+ @xml_element.add_element(env_vars.xml_element) if env_vars
102
+ env_vars
103
+ end
104
+
87
105
  #-------------------------------------------------------------------------#
88
106
 
89
107
  class TestableReference < XMLElementWrapper
@@ -1,24 +1,54 @@
1
1
  require 'fileutils'
2
2
  require 'rexml/document'
3
3
  require 'xcodeproj/workspace/file_reference'
4
+ require 'xcodeproj/workspace/group_reference'
4
5
 
5
6
  module Xcodeproj
6
7
  # Provides support for generating, reading and serializing Xcode Workspace
7
8
  # documents.
8
9
  #
9
10
  class Workspace
10
- # @return [Array<String>] the paths of the projects contained in the
11
+ # @return [REXML::Document] the parsed XML model for the workspace contents
12
+ attr_reader :document
13
+
14
+ # @return [Hash<String => String>] a mapping from scheme name to project full path
15
+ # containing the scheme
16
+ attr_reader :schemes
17
+
11
18
  # @return [Array<FileReference>] the paths of the projects contained in the
12
19
  # workspace.
13
20
  #
14
- attr_reader :file_references
15
- attr_reader :schemes
21
+ def file_references
22
+ return [] unless @document
23
+ @document.get_elements('/Workspace//FileRef').map do |node|
24
+ FileReference.from_node(node)
25
+ end
26
+ end
27
+
28
+ # @return [Array<GroupReference>] the groups contained in the workspace
29
+ #
30
+ def group_references
31
+ return [] unless @document
32
+ @document.get_elements('/Workspace//Group').map do |node|
33
+ GroupReference.from_node(node)
34
+ end
35
+ end
16
36
 
17
- # @param [Array] file_references @see file_references
37
+ # @param [REXML::Document] document @see document
38
+ # @param [Array<FileReference>] file_references additional projects to add
39
+ #
40
+ # @note The document parameter is passed to the << operator if it is not a
41
+ # valid REXML::Document. It is optional, but may also be passed as nil
18
42
  #
19
- def initialize(*file_references)
20
- @file_references = file_references.flatten
43
+ def initialize(document, *file_references)
21
44
  @schemes = {}
45
+ if document.nil? || document.is_a?(REXML::Document)
46
+ @document = document
47
+ else
48
+ @document = REXML::Document.new(root_xml(''))
49
+ self << document
50
+ end
51
+ file_references.each { |ref| self << ref }
22
52
  end
23
53
 
24
54
  #-------------------------------------------------------------------------#
@@ -31,9 +61,10 @@ module Xcodeproj
31
61
  # @return [Workspace] the generated workspace.
32
62
  #
33
63
  def self.new_from_xcworkspace(path)
34
- from_s(File.read(File.join(path, 'contents.xcworkspacedata')), File.expand_path(File.dirname(path)))
64
+ from_s(File.read(File.join(path, 'contents.xcworkspacedata')),
65
+ File.expand_path(File.dirname(path)))
35
66
  rescue Errno::ENOENT
36
- new
67
+ new(nil)
37
68
  end
38
69
 
39
70
  #-------------------------------------------------------------------------#
@@ -48,28 +79,53 @@ module Xcodeproj
48
79
  #
49
80
  def self.from_s(xml, workspace_path = '')
50
81
  document = REXML::Document.new(xml)
51
- file_references = document.get_elements('/Workspace/FileRef').map do |node|
52
- FileReference.from_node(node)
53
- end
54
- instance = new(file_references)
82
+ instance = new(document)
55
83
  instance.load_schemes(workspace_path)
56
84
  instance
57
85
  end
58
86
 
59
- #-------------------------------------------------------------------------#
60
-
61
87
  # Adds a new path to the list of the of projects contained in the
62
88
  # workspace.
89
+ # @param [String, Xcodeproj::Workspace::FileReference] path_or_reference
90
+ # A string or Xcode::Workspace::FileReference containing a path to an Xcode project
63
91
  #
64
- # @param [String] projpath
65
- # The path of the project to add.
92
+ # @raise [ArgumentError] Raised if the input is neither a String nor a FileReference
66
93
  #
67
94
  # @return [void]
68
95
  #
69
- def <<(projpath)
70
- project_file_reference = Xcodeproj::Workspace::FileReference.new(projpath)
71
- @file_references << project_file_reference
72
- load_schemes_from_project File.expand_path(projpath)
96
+ def <<(path_or_reference)
97
+ return unless @document && @document.respond_to?(:root)
98
+ case
99
+ when path_or_reference.is_a?(String)
100
+ project_file_reference = Xcodeproj::Workspace::FileReference.new(path_or_reference)
101
+ when path_or_reference.is_a?(Xcodeproj::Workspace::FileReference)
102
+ project_file_reference = path_or_reference
103
+ projpath = nil
104
+ else
105
+ raise ArgumentError, 'Input to the << operator must be a file path or FileReference'
106
+ end
107
+ @document.root.add_element(project_file_reference.to_node)
108
+ load_schemes_from_project File.expand_path(projpath || project_file_reference.path)
109
+ end
110
+
111
+ #-------------------------------------------------------------------------#
112
+
113
+ # Adds a new group container to the workspace
114
+ # workspace.
115
+ #
116
+ # @param [String] name The name of the group
117
+ #
118
+ # @yield [Xcodeproj::Workspace::GroupReference, REXML::Element]
119
+ # Yields the GroupReference and underlying XML element for mutation
120
+ #
121
+ # @return [Xcodeproj::Workspace::GroupReference] The added group reference
122
+ #
123
+ def add_group(name)
124
+ return nil unless @document
125
+ group = Xcodeproj::Workspace::GroupReference.new(name)
126
+ elem = @document.root.add_element(group.to_node)
127
+ yield group, elem if block_given?
128
+ group
73
129
  end
74
130
 
75
131
  # Checks if the workspace contains the project with the given file
@@ -81,14 +137,32 @@ module Xcodeproj
81
137
  # @return [Boolean] whether the project is contained in the workspace.
82
138
  #
83
139
  def include?(file_reference)
84
- @file_references.include?(file_reference)
140
+ file_references.include?(file_reference)
85
141
  end
86
142
 
87
143
  # @return [String] the XML representation of the workspace.
88
144
  #
89
145
  def to_s
90
- contents = file_references.map { |reference| file_reference_xml(reference) }
91
- root_xml(contents.join(''))
146
+ contents = ''
147
+ stack = []
148
+ @document.root.each_recursive do |elem|
149
+ until stack.empty?
150
+ last = stack.last
151
+ break if last == elem.parent
152
+ contents += xcworkspace_element_end_xml(stack.length, last)
153
+ stack.pop
154
+ end
155
+
156
+ stack << elem
157
+ contents += xcworkspace_element_start_xml(stack.length, elem)
158
+ end
159
+
160
+ until stack.empty?
161
+ contents += xcworkspace_element_end_xml(stack.length, stack.last)
162
+ stack.pop
163
+ end
164
+
165
+ root_xml(contents)
92
166
  end
93
167
 
94
168
  # Saves the workspace at the given `xcworkspace` path.
@@ -115,7 +189,7 @@ module Xcodeproj
115
189
  # @return [void]
116
190
  #
117
191
  def load_schemes(workspace_dir_path)
118
- @file_references.each do |file_reference|
192
+ file_references.each do |file_reference|
119
193
  project_full_path = file_reference.absolute_path(workspace_dir_path)
120
194
  load_schemes_from_project(project_full_path)
121
195
  end
@@ -146,21 +220,38 @@ module Xcodeproj
146
220
  <?xml version="1.0" encoding="UTF-8"?>
147
221
  <Workspace
148
222
  version = "1.0">
149
- #{contents.strip}
223
+ #{contents.rstrip}
150
224
  </Workspace>
151
- DOC
225
+ DOC
152
226
  end
153
227
 
154
- # @return [String] The XML representation of a file reference.
155
228
  #
156
- # @param [FileReference] file_reference The file reference.
229
+ # @param [Integer] depth The depth of the element in the tree
230
+ # @param [REXML::Document::Element] elem The XML element to format.
157
231
  #
158
- def file_reference_xml(file_reference)
159
- <<-DOC
160
- <FileRef
161
- location = "#{file_reference.type}:#{file_reference.path}">
162
- </FileRef>
163
- DOC
232
+ # @return [String] The Xcode-specific XML formatting of an element start
233
+ #
234
+ def xcworkspace_element_start_xml(depth, elem)
235
+ attributes = case elem.name
236
+ when 'Group'
237
+ %w(location name)
238
+ when 'FileRef'
239
+ %w(location)
240
+ end
241
+ contents = "<#{elem.name}"
242
+ indent = ' ' * depth
243
+ attributes.each { |name| contents += "\n #{name} = \"#{elem.attribute(name)}\"" }
244
+ contents.split("\n").map { |line| "#{indent}#{line}" }.join("\n") + ">\n"
245
+ end
246
+
247
+ #
248
+ # @param [Integer] depth The depth of the element in the tree
249
+ # @param [REXML::Document::Element] elem The XML element to format.
250
+ #
251
+ # @return [String] The Xcode-specific XML formatting of an element end
252
+ #
253
+ def xcworkspace_element_end_xml(depth, elem)
254
+ "#{' ' * depth}</#{elem.name}>\n"
164
255
  end
165
256
 
166
257
  #-------------------------------------------------------------------------#
@@ -0,0 +1,64 @@
1
+ module Xcodeproj
2
+ class Workspace
3
+ # Describes a group reference of a Workspace.
4
+ #
5
+ class GroupReference
6
+ # @return [String] the name of the group
7
+ #
8
+ attr_reader :name
9
+
10
+ # @return [String] the type of reference to the project
11
+ #
12
+ # This can be of the following values:
13
+ # - absolute
14
+ # - group
15
+ # - container (only supported value)
16
+ # - developer
17
+ #
18
+ attr_reader :type
19
+
20
+ # @param [#to_s] name @see name
21
+ # @param [#to_s] type @see type
22
+ #
23
+ def initialize(name, type = 'container')
24
+ @name = name.to_s
25
+ @type = type.to_s
26
+ end
27
+
28
+ # @return [Bool] Whether a group reference is equal to another.
29
+ #
30
+ def ==(other)
31
+ name == other.name && type == other.type
32
+ end
33
+ alias_method :eql?, :==
34
+
35
+ # @return [Fixnum] A hash identical for equals objects.
36
+ #
37
+ def hash
38
+ [name, type].hash
39
+ end
40
+
41
+ # Returns a group reference given XML representation.
42
+ #
43
+ # @param [REXML::Element] xml_node
44
+ # the XML representation.
45
+ #
46
+ # @return [GroupReference] The new group reference instance.
47
+ #
48
+ def self.from_node(xml_node)
49
+ type = xml_node.attribute('location').value.split(':', 2).first
50
+ name = xml_node.attribute('name').value
51
+ new(name, type)
52
+ end
53
+
54
+ # @return [REXML::Element] the XML representation of the group reference.
55
+ #
56
+ def to_node
57
+ REXML::Element.new('Group').tap do |element|
58
+ element.add_attribute('location', "#{type}:")
59
+ element.add_attribute('name', "#{name}")
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xcodeproj
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.28.2
4
+ version: 1.0.0.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eloy Duran
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-09 00:00:00.000000000 Z
11
+ date: 2015-12-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -61,22 +61,30 @@ executables:
61
61
  extensions: []
62
62
  extra_rdoc_files: []
63
63
  files:
64
- - README.md
65
64
  - LICENSE
65
+ - README.md
66
+ - bin/xcodeproj
67
+ - lib/xcodeproj.rb
68
+ - lib/xcodeproj/command.rb
66
69
  - lib/xcodeproj/command/config_dump.rb
67
70
  - lib/xcodeproj/command/project_diff.rb
68
71
  - lib/xcodeproj/command/show.rb
69
72
  - lib/xcodeproj/command/sort.rb
70
73
  - lib/xcodeproj/command/target_diff.rb
71
- - lib/xcodeproj/command.rb
72
- - lib/xcodeproj/config/other_linker_flags_parser.rb
73
74
  - lib/xcodeproj/config.rb
75
+ - lib/xcodeproj/config/other_linker_flags_parser.rb
74
76
  - lib/xcodeproj/constants.rb
75
77
  - lib/xcodeproj/differ.rb
76
78
  - lib/xcodeproj/gem_version.rb
77
79
  - lib/xcodeproj/helper.rb
78
- - lib/xcodeproj/plist_helper.rb
80
+ - lib/xcodeproj/plist.rb
81
+ - lib/xcodeproj/plist/ffi.rb
82
+ - lib/xcodeproj/plist/ffi/core_foundation.rb
83
+ - lib/xcodeproj/plist/ffi/dev_tools_core.rb
84
+ - lib/xcodeproj/plist/plist_gem.rb
85
+ - lib/xcodeproj/project.rb
79
86
  - lib/xcodeproj/project/case_converter.rb
87
+ - lib/xcodeproj/project/object.rb
80
88
  - lib/xcodeproj/project/object/build_configuration.rb
81
89
  - lib/xcodeproj/project/object/build_file.rb
82
90
  - lib/xcodeproj/project/object/build_phase.rb
@@ -91,31 +99,29 @@ files:
91
99
  - lib/xcodeproj/project/object/reference_proxy.rb
92
100
  - lib/xcodeproj/project/object/root_object.rb
93
101
  - lib/xcodeproj/project/object/target_dependency.rb
94
- - lib/xcodeproj/project/object.rb
95
102
  - lib/xcodeproj/project/object_attributes.rb
96
103
  - lib/xcodeproj/project/object_dictionary.rb
97
104
  - lib/xcodeproj/project/object_list.rb
98
105
  - lib/xcodeproj/project/project_helper.rb
99
106
  - lib/xcodeproj/project/uuid_generator.rb
100
- - lib/xcodeproj/project.rb
107
+ - lib/xcodeproj/scheme.rb
101
108
  - lib/xcodeproj/scheme/abstract_scheme_action.rb
102
109
  - lib/xcodeproj/scheme/analyze_action.rb
103
110
  - lib/xcodeproj/scheme/archive_action.rb
104
111
  - lib/xcodeproj/scheme/build_action.rb
105
112
  - lib/xcodeproj/scheme/buildable_product_runnable.rb
106
113
  - lib/xcodeproj/scheme/buildable_reference.rb
114
+ - lib/xcodeproj/scheme/environment_variables.rb
107
115
  - lib/xcodeproj/scheme/launch_action.rb
108
116
  - lib/xcodeproj/scheme/macro_expansion.rb
109
117
  - lib/xcodeproj/scheme/profile_action.rb
110
118
  - lib/xcodeproj/scheme/test_action.rb
111
119
  - lib/xcodeproj/scheme/xml_element_wrapper.rb
112
- - lib/xcodeproj/scheme.rb
113
120
  - lib/xcodeproj/user_interface.rb
114
- - lib/xcodeproj/workspace/file_reference.rb
115
121
  - lib/xcodeproj/workspace.rb
122
+ - lib/xcodeproj/workspace/file_reference.rb
123
+ - lib/xcodeproj/workspace/group_reference.rb
116
124
  - lib/xcodeproj/xcodebuild_helper.rb
117
- - lib/xcodeproj.rb
118
- - bin/xcodeproj
119
125
  homepage: https://github.com/cocoapods/xcodeproj
120
126
  licenses:
121
127
  - MIT
@@ -136,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
142
  version: '0'
137
143
  requirements: []
138
144
  rubyforge_project:
139
- rubygems_version: 2.0.14.1
145
+ rubygems_version: 2.5.1
140
146
  signing_key:
141
147
  specification_version: 3
142
148
  summary: Create and modify Xcode projects from Ruby.
@@ -1,758 +0,0 @@
1
- begin
2
- require 'fiddle'
3
- rescue LoadError
4
- message = 'Xcodeproj relies on a library called `fiddle` to read and write ' \
5
- 'Xcode project files. Ensure your Ruby installation includes ' \
6
- '`fiddle` and try again.'
7
- raise Xcodeproj::Informative, message
8
- end
9
-
10
- module Xcodeproj
11
- # TODO: Delete me (compatibility with Ruby 1.8.7 C ext bundle)
12
- def self.read_plist(path)
13
- PlistHelper.read(path)
14
- end
15
-
16
- # TODO: Delete me (compatibility with Ruby 1.8.7 C ext bundle)
17
- def self.write_plist(hash, path)
18
- PlistHelper.write(hash, path)
19
- end
20
-
21
- # Provides support for loading and serializing property list files.
22
- #
23
- module PlistHelper
24
- class << self
25
- # Serializes a hash as an XML property list file.
26
- #
27
- # @param [#to_hash] hash
28
- # The hash to store.
29
- #
30
- # @param [#to_s] path
31
- # The path of the file.
32
- #
33
- def write(possible_hash, path)
34
- if possible_hash.respond_to?(:to_hash)
35
- hash = possible_hash.to_hash
36
- else
37
- raise TypeError, "The given `#{possible_hash.inspect}` must respond " \
38
- "to #to_hash'."
39
- end
40
-
41
- unless path.is_a?(String) || path.is_a?(Pathname)
42
- raise TypeError, "The given `#{path}` must be a string or 'pathname'."
43
- end
44
- path = path.to_s
45
- raise IOError, 'Empty path.' if path == ''
46
-
47
- if DevToolsCore.load_xcode_frameworks && path.end_with?('pbxproj')
48
- ruby_hash_write_xcode(hash, path)
49
- else
50
- CoreFoundation.RubyHashPropertyListWrite(hash, path)
51
- fix_encoding(path)
52
- end
53
- end
54
-
55
- # @return [Hash] Returns the native objects loaded from a property list
56
- # file.
57
- #
58
- # @param [#to_s] path
59
- # The path of the file.
60
- #
61
- def read(path)
62
- path = path.to_s
63
- unless File.exist?(path)
64
- raise Informative, "The plist file at path `#{path}` doesn't exist."
65
- end
66
- if file_in_conflict?(path)
67
- raise Informative, "The file `#{path}` is in a merge conflict."
68
- end
69
- CoreFoundation.RubyHashPropertyListRead(path)
70
- end
71
-
72
- private
73
-
74
- # Simple workaround to escape characters which are outside of ASCII
75
- # character-encoding. Relies on the fact that there are no XML characters
76
- # which would need to be escaped.
77
- #
78
- # @note This is necessary because Xcode (4.6 currently) uses the MacRoman
79
- # encoding unless the `// !$*UTF8*$!` magic comment is present. It
80
- # is not possible to serialize a plist using the NeXTSTEP format
81
- # without access to the private classes of Xcode and that comment
82
- # is not compatible with the XML format. For the complete
83
- # discussion see CocoaPods/CocoaPods#926.
84
- #
85
- #
86
- # @note Sadly this hack is not sufficient for supporting Emoji.
87
- #
88
- # @param [String, Pathname] The path of the file which needs to be fixed.
89
- #
90
- # @return [void]
91
- #
92
- def fix_encoding(filename)
93
- output = ''
94
- input = File.open(filename, 'rb') { |file| file.read }
95
- input.unpack('U*').each do |codepoint|
96
- if codepoint > 127 # ASCII is 7-bit, so 0-127 are valid characters
97
- output << "&##{codepoint};"
98
- else
99
- output << codepoint.chr
100
- end
101
- end
102
- File.open(filename, 'wb') { |file| file.write(output) }
103
- end
104
-
105
- # @return [Bool] Checks whether there are merge conflicts in the file.
106
- #
107
- # @param [#to_s] path
108
- # The path of the file.
109
- #
110
- def file_in_conflict?(path)
111
- file = File.open(path)
112
- file.each_line.any? { |l| l.match(/^(<|=|>){7}/) }
113
- ensure
114
- file.close
115
- end
116
-
117
- # Serializes a hash as an ASCII plist, using Xcode.
118
- #
119
- # @param [Hash] hash
120
- # The hash to store.
121
- #
122
- # @param [String] path
123
- # The path of the file.
124
- #
125
- def ruby_hash_write_xcode(hash, path)
126
- path = File.expand_path(path)
127
- success = true
128
-
129
- begin
130
- plist = DevToolsCore::CFDictionary.new(CoreFoundation.RubyHashToCFDictionary(hash))
131
- data = DevToolsCore::NSData.new(plist.plistDescriptionUTF8Data)
132
- success &= data.writeToFileAtomically(path)
133
-
134
- project = DevToolsCore::PBXProject.new(path)
135
- success &= project.writeToFileSystemProjectFile
136
- project.close
137
- rescue Fiddle::DLError
138
- success = false
139
- end
140
-
141
- CoreFoundation.RubyHashPropertyListWrite(hash, path) unless success
142
- end
143
- end
144
- end
145
- end
146
-
147
- # This module provides an interface to the CoreFoundation OS X framework.
148
- # Specifically it bridges the functions required to be able to read and write
149
- # property lists.
150
- #
151
- # Everything in here should be considered an implementation detail and thus is
152
- # not further documented.
153
- #
154
- # @todo Move this out into its own gem.
155
- #
156
- # @!visibility private
157
- #
158
- module CoreFoundation
159
- PATH = '/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation'
160
-
161
- # rubocop:disable Style/MethodName
162
- # rubocop:disable Style/VariableName
163
-
164
- # @!group Ruby hash as property list (de)serialization
165
- #---------------------------------------------------------------------------#
166
-
167
- def self.RubyHashPropertyListWrite(hash, path)
168
- url = CFURLCreateFromFileSystemRepresentation(NULL,
169
- path,
170
- path.bytesize,
171
- FALSE)
172
- stream = CFWriteStreamCreateWithFile(NULL, url)
173
- unless CFWriteStreamOpen(stream) == TRUE
174
- raise IOError, 'Unable to open stream.'
175
- end
176
-
177
- plist = RubyHashToCFDictionary(hash)
178
-
179
- error_ptr = CFTypeRefPointer()
180
- result = CFPropertyListWrite(plist,
181
- stream,
182
- KCFPropertyListXMLFormat_v1_0,
183
- 0,
184
- error_ptr)
185
- CFWriteStreamClose(stream)
186
-
187
- if result == 0
188
- description = CFCopyDescription(error_ptr.ptr)
189
- raise IOError, "Unable to write plist data: #{description}"
190
- end
191
- result
192
- end
193
-
194
- def self.RubyHashPropertyListRead(path)
195
- url = CFURLCreateFromFileSystemRepresentation(NULL,
196
- path,
197
- path.bytesize,
198
- FALSE)
199
- stream = CFReadStreamCreateWithFile(NULL, url)
200
- unless CFReadStreamOpen(stream) == TRUE
201
- raise IOError, 'Unable to open stream.'
202
- end
203
-
204
- error_ptr = CFTypeRefPointer()
205
- plist = CFPropertyListCreateWithStream(NULL,
206
- stream,
207
- 0,
208
- KCFPropertyListImmutable,
209
- NULL,
210
- error_ptr)
211
- CFReadStreamClose(stream)
212
-
213
- if plist.null?
214
- description = CFCopyDescription(error_ptr.ptr)
215
- raise IOError, "Unable to read plist data: #{description}"
216
- elsif CFGetTypeID(plist) != CFDictionaryGetTypeID()
217
- raise TypeError, 'Expected a plist with a dictionary root object.'
218
- end
219
-
220
- CFDictionaryToRubyHash(plist)
221
- end
222
-
223
- # @!group Types
224
- #---------------------------------------------------------------------------#
225
-
226
- # rubocop:disable Style/ConstantName
227
-
228
- NULL = Fiddle::NULL
229
-
230
- Void = Fiddle::TYPE_VOID
231
- VoidPointer = Fiddle::TYPE_VOIDP
232
- FunctionPointer = VoidPointer
233
-
234
- UInt32 = -Fiddle::TYPE_INT
235
- UInt8 = -Fiddle::TYPE_CHAR
236
-
237
- SInt32Pointer = VoidPointer
238
- UInt8Pointer = VoidPointer
239
- CharPointer = VoidPointer
240
-
241
- Boolean = Fiddle::TYPE_CHAR
242
- TRUE = 1
243
- FALSE = 0
244
-
245
- SINT64_MAX = 2**63 - 1
246
- SINT64_MIN = -SINT64_MAX - 1
247
-
248
- SIZEOF_SINT64 = 8
249
- SIZEOF_FLOAT64 = 8
250
-
251
- SINT64_PACK_TEMPLATE = 'q'
252
- FLOAT64_PACK_TEMPLATE = 'd'
253
-
254
- CFTypeRef = VoidPointer
255
- CFTypeRefPointer = VoidPointer
256
- CFIndex = Fiddle::TYPE_LONG
257
- CFTypeID = -Fiddle::TYPE_LONG
258
- CFOptionFlags = UInt32
259
-
260
- CFPropertyListMutabilityOptions = Fiddle::TYPE_INT
261
- KCFPropertyListImmutable = 0
262
-
263
- CFPropertyListFormat = Fiddle::TYPE_INT
264
- KCFPropertyListXMLFormat_v1_0 = 100
265
- CFPropertyListFormatPointer = VoidPointer
266
-
267
- CFStringEncoding = UInt32
268
- KCFStringEncodingUTF8 = 0x08000100
269
-
270
- CFNumberType = Fiddle::TYPE_INT
271
- KCFNumberSInt64Type = 4
272
- KCFNumberFloat64Type = 6
273
-
274
- # rubocop:enable Style/ConstantName
275
-
276
- private
277
-
278
- # @!group Helpers
279
- #---------------------------------------------------------------------------#
280
-
281
- def self.image
282
- @image ||= Fiddle.dlopen(PATH)
283
- end
284
-
285
- def self.free_function
286
- @free_function ||= Fiddle::Function.new(Fiddle::Handle.new['free'], [VoidPointer], Void)
287
- end
288
-
289
- def self.CFRelease_function
290
- @CFRelease ||= Fiddle::Function.new(image['CFRelease'], [CFTypeRef], Void)
291
- end
292
-
293
- def self.extern_image(image, symbol, parameter_types, return_type)
294
- symbol = symbol.to_s
295
- create_function = symbol.include?('Create')
296
- function_cache_key = "@__#{symbol}__"
297
-
298
- # Define a singleton method on the CoreFoundation module.
299
- define_singleton_method(symbol) do |*args|
300
- unless args.size == parameter_types.size
301
- raise ArgumentError, "wrong number of arguments (#{args.size} for " \
302
- "#{parameter_types.size})"
303
- end
304
-
305
- unless function = instance_variable_get(function_cache_key)
306
- function = Fiddle::Function.new(image[symbol],
307
- parameter_types,
308
- return_type)
309
- instance_variable_set(function_cache_key, function)
310
- end
311
-
312
- result = function.call(*args)
313
- create_function ? CFAutoRelease(result) : result
314
- end
315
- end
316
-
317
- def self.extern(symbol, parameter_types, return_type)
318
- extern_image(image, symbol, parameter_types, return_type)
319
- end
320
-
321
- public
322
-
323
- # @!group CoreFoundation function definitions
324
- #---------------------------------------------------------------------------#
325
-
326
- # CFTypeRef description
327
- extern :CFShow, [CFTypeRef], Void
328
- extern :CFCopyDescription, [CFTypeRef], CFTypeRef
329
-
330
- # CFType reflection
331
- extern :CFGetTypeID, [CFTypeRef], CFTypeID
332
- extern :CFDictionaryGetTypeID, [], CFTypeID
333
- extern :CFStringGetTypeID, [], CFTypeID
334
- extern :CFArrayGetTypeID, [], CFTypeID
335
- extern :CFBooleanGetTypeID, [], CFTypeID
336
- extern :CFNumberGetTypeID, [], CFTypeID
337
-
338
- # CFStream
339
- extern :CFWriteStreamCreateWithFile, [CFTypeRef, CFTypeRef], CFTypeRef
340
- extern :CFWriteStreamOpen, [CFTypeRef], Boolean
341
- extern :CFWriteStreamClose, [CFTypeRef], Void
342
- extern :CFReadStreamCreateWithFile, [CFTypeRef, CFTypeRef], CFTypeRef
343
- extern :CFReadStreamOpen, [CFTypeRef], Boolean
344
- extern :CFReadStreamClose, [CFTypeRef], Void
345
-
346
- # CFURL
347
- extern :CFURLCreateFromFileSystemRepresentation, [CFTypeRef, UInt8Pointer, CFIndex, Boolean], CFTypeRef
348
-
349
- # CFPropertyList
350
- extern :CFPropertyListWrite, [CFTypeRef, CFTypeRef, CFPropertyListFormat, CFOptionFlags, CFTypeRefPointer], CFIndex
351
- extern :CFPropertyListCreateWithStream, [CFTypeRef, CFTypeRef, CFIndex, CFOptionFlags, CFPropertyListFormatPointer, CFTypeRefPointer], CFTypeRef
352
-
353
- # CFString
354
- extern :CFStringCreateExternalRepresentation, [CFTypeRef, CFTypeRef, CFStringEncoding, UInt8], CFTypeRef
355
- extern :CFStringCreateWithCString, [CFTypeRef, CharPointer, CFStringEncoding], CFTypeRef
356
-
357
- # CFData
358
- extern :CFDataGetLength, [CFTypeRef], CFIndex
359
- extern :CFDataGetBytePtr, [CFTypeRef], VoidPointer
360
-
361
- # CFDictionary
362
- extern :CFDictionaryCreateMutable, [CFTypeRef, CFIndex, VoidPointer, VoidPointer], CFTypeRef
363
- extern :CFDictionaryAddValue, [CFTypeRef, CFTypeRef, CFTypeRef], VoidPointer
364
- extern :CFDictionaryApplyFunction, [CFTypeRef, FunctionPointer, VoidPointer], Void
365
-
366
- # CFArray
367
- extern :CFArrayCreateMutable, [CFTypeRef, CFIndex, VoidPointer], CFTypeRef
368
- extern :CFArrayAppendValue, [CFTypeRef, CFTypeRef], VoidPointer
369
- extern :CFArrayGetCount, [CFTypeRef], CFIndex
370
- extern :CFArrayGetValueAtIndex, [CFTypeRef, CFIndex], CFTypeRef
371
-
372
- # CFBoolean
373
- extern :CFBooleanGetValue, [CFTypeRef], Boolean
374
-
375
- # CFNumber
376
- extern :CFNumberIsFloatType, [CFTypeRef], Boolean
377
- extern :CFNumberGetValue, [CFTypeRef, CFNumberType, VoidPointer], Boolean
378
- extern :CFNumberCreate, [CFTypeRef, CFNumberType, VoidPointer], CFTypeRef
379
-
380
- # @!group Custom convenience functions
381
- #---------------------------------------------------------------------------#
382
-
383
- def self.CFBooleanTrue
384
- @CFBooleanTrue ||= Fiddle::Pointer.new(image['kCFBooleanTrue']).ptr
385
- end
386
-
387
- def self.CFBooleanFalse
388
- @CFBooleanFalse ||= Fiddle::Pointer.new(image['kCFBooleanFalse']).ptr
389
- end
390
-
391
- def self.CFTypeArrayCallBacks
392
- @CFTypeArrayCallBacks ||= image['kCFTypeArrayCallBacks']
393
- end
394
-
395
- def self.CFTypeDictionaryKeyCallBacks
396
- @CFTypeDictionaryKeyCallBacks ||= image['kCFTypeDictionaryKeyCallBacks']
397
- end
398
-
399
- def self.CFTypeDictionaryValueCallBacks
400
- @CFTypeDictionaryValueCallBacks ||= image['kCFTypeDictionaryValueCallBacks']
401
- end
402
-
403
- # This pointer will assign `CFRelease` as the free function when
404
- # dereferencing the pointer.
405
- #
406
- # @note This means that the object will *not* be released if it's not
407
- # dereferenced, but that would be a leak anyways, so be sure to do so.
408
- #
409
- def self.CFTypeRefPointer
410
- pointer = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INTPTR_T, free_function)
411
- def pointer.ptr
412
- ::CoreFoundation.CFAutoRelease(super)
413
- end
414
- pointer
415
- end
416
-
417
- def self.CFAutoRelease(cf_type_reference)
418
- cf_type_reference.free = CFRelease_function() unless cf_type_reference.null?
419
- cf_type_reference
420
- end
421
-
422
- def self.CFDictionaryApplyBlock(dictionary, &applier)
423
- param_types = [CFTypeRef, CFTypeRef, VoidPointer]
424
- closure = Fiddle::Closure::BlockCaller.new(Void, param_types, &applier)
425
- closure_function = Fiddle::Function.new(closure, param_types, Void)
426
- CFDictionaryApplyFunction(dictionary, closure_function, NULL)
427
- end
428
-
429
- def self.CFArrayApplyBlock(array)
430
- CFArrayGetCount(array).times do |index|
431
- yield CFArrayGetValueAtIndex(array, index)
432
- end
433
- end
434
-
435
- # @!group CFTypeRef to Ruby value conversion
436
- #---------------------------------------------------------------------------#
437
-
438
- def self.CFTypeRefToRubyValue(cf_type_reference)
439
- case CFGetTypeID(cf_type_reference)
440
- when CFStringGetTypeID()
441
- CFStringToRubyString(cf_type_reference)
442
- when CFDictionaryGetTypeID()
443
- CFDictionaryToRubyHash(cf_type_reference)
444
- when CFArrayGetTypeID()
445
- CFArrayToRubyArray(cf_type_reference)
446
- when CFBooleanGetTypeID()
447
- CFBooleanToRubyBoolean(cf_type_reference)
448
- when CFNumberGetTypeID()
449
- CFNumberToRubyNumber(cf_type_reference)
450
- else
451
- description = CFStringToRubyString(CFCopyDescription(cf_type_reference))
452
- raise TypeError, "Unknown type: #{description}"
453
- end
454
- end
455
-
456
- def self.CFStringToRubyString(string)
457
- data = CFStringCreateExternalRepresentation(NULL,
458
- string,
459
- KCFStringEncodingUTF8,
460
- 0)
461
- if data.null?
462
- raise TypeError, 'Unable to convert CFStringRef.'
463
- end
464
- bytes_ptr = CFDataGetBytePtr(data)
465
- result = bytes_ptr.to_str(CFDataGetLength(data))
466
- result.force_encoding(Encoding::UTF_8)
467
- result
468
- end
469
-
470
- def self.CFDictionaryToRubyHash(dictionary)
471
- result = {}
472
- CFDictionaryApplyBlock(dictionary) do |key, value|
473
- result[CFStringToRubyString(key)] = CFTypeRefToRubyValue(value)
474
- end
475
- result
476
- end
477
-
478
- def self.CFArrayToRubyArray(array)
479
- result = []
480
- CFArrayApplyBlock(array) do |element|
481
- result << CFTypeRefToRubyValue(element)
482
- end
483
- result
484
- end
485
-
486
- def self.CFBooleanToRubyBoolean(boolean)
487
- CFBooleanGetValue(boolean) == TRUE
488
- end
489
-
490
- def self.CFNumberToRubyNumber(number)
491
- if CFNumberIsFloatType(number) == FALSE
492
- value_type = KCFNumberSInt64Type
493
- pack_template = SINT64_PACK_TEMPLATE
494
- size = SIZEOF_SINT64
495
- else
496
- value_type = KCFNumberFloat64Type
497
- pack_template = FLOAT64_PACK_TEMPLATE
498
- size = SIZEOF_FLOAT64
499
- end
500
- ptr = Fiddle::Pointer.malloc(size)
501
- CFNumberGetValue(number, value_type, ptr)
502
- ptr.to_str.unpack(pack_template).first
503
- end
504
-
505
- # @!group Ruby value to CFTypeRef conversion
506
- #---------------------------------------------------------------------------#
507
-
508
- def self.RubyValueToCFTypeRef(value)
509
- result = case value
510
- when String
511
- RubyStringToCFString(value)
512
- when Hash
513
- RubyHashToCFDictionary(value)
514
- when Array
515
- RubyArrayToCFArray(value)
516
- when true, false
517
- RubyBooleanToCFBoolean(value)
518
- when Numeric
519
- RubyNumberToCFNumber(value)
520
- else
521
- RubyStringToCFString(value.to_s)
522
- end
523
- if result.null?
524
- raise TypeError, "Unable to convert Ruby value `#{value.inspect}' " \
525
- 'into a CFTypeRef.'
526
- end
527
- result
528
- end
529
-
530
- def self.RubyStringToCFString(string)
531
- CFStringCreateWithCString(NULL,
532
- Fiddle::Pointer[string],
533
- KCFStringEncodingUTF8)
534
- end
535
-
536
- def self.RubyHashToCFDictionary(hash)
537
- result = CFDictionaryCreateMutable(NULL,
538
- 0,
539
- CFTypeDictionaryKeyCallBacks(),
540
- CFTypeDictionaryValueCallBacks())
541
- hash.each do |key, value|
542
- key = RubyStringToCFString(key.to_s)
543
- value = RubyValueToCFTypeRef(value)
544
- CFDictionaryAddValue(result, key, value)
545
- end
546
- result
547
- end
548
-
549
- def self.RubyArrayToCFArray(array)
550
- result = CFArrayCreateMutable(NULL, 0, CFTypeArrayCallBacks())
551
- array.each do |element|
552
- element = RubyValueToCFTypeRef(element)
553
- CFArrayAppendValue(result, element)
554
- end
555
- result
556
- end
557
-
558
- def self.RubyNumberToCFNumber(value)
559
- case value
560
- when Float
561
- value_type = KCFNumberFloat64Type
562
- pack_template = FLOAT64_PACK_TEMPLATE
563
- when SINT64_MIN..SINT64_MAX
564
- value_type = KCFNumberSInt64Type
565
- pack_template = SINT64_PACK_TEMPLATE
566
- else # the value is too big to be stored in a CFNumber so store it as a CFString
567
- return RubyStringToCFString(value.to_s)
568
- end
569
- ptr = Fiddle::Pointer.to_ptr([value].pack(pack_template))
570
- CFNumberCreate(NULL, value_type, ptr)
571
- end
572
-
573
- def self.RubyBooleanToCFBoolean(value)
574
- value ? CFBooleanTrue() : CFBooleanFalse()
575
- end
576
-
577
- # rubocop:enable Style/MethodName
578
- # rubocop:enable Style/VariableName
579
- end
580
-
581
- module DevToolsCore
582
- def self.silence_stderr
583
- begin
584
- orig_stderr = $stderr.clone
585
- $stderr.reopen File.new('/dev/null', 'w')
586
- retval = yield
587
- ensure
588
- $stderr.reopen orig_stderr
589
- end
590
- retval
591
- end
592
-
593
- # rubocop:disable Style/MethodName
594
- # rubocop:disable Style/VariableName
595
-
596
- class NSObject
597
- private
598
-
599
- def self.objc_class
600
- @objc_class ||= CoreFoundation.objc_getClass(name.split('::').last)
601
- end
602
-
603
- def self.image
604
- @image ||= Fiddle::Handle.new
605
- end
606
-
607
- def self.extern(symbol, parameter_types, return_type)
608
- CoreFoundation.extern_image(image, symbol, parameter_types, return_type)
609
- end
610
-
611
- def self.objc_msgSend(args, return_type = CoreFoundation::VoidPointer)
612
- arguments = [CoreFoundation::VoidPointer, CoreFoundation::VoidPointer] + args
613
-
614
- Fiddle::Function.new(image['objc_msgSend'], arguments, return_type)
615
- end
616
-
617
- def self.respondsToSelector(instance, sel)
618
- selector = CoreFoundation.NSSelectorFromString(CoreFoundation.RubyStringToCFString(sel))
619
- respondsToSelector = objc_msgSend([CoreFoundation::CharPointer], CoreFoundation::Boolean)
620
- result = respondsToSelector.call(
621
- instance,
622
- CoreFoundation.NSSelectorFromString(CoreFoundation.RubyStringToCFString('respondsToSelector:')),
623
- selector)
624
- result == CoreFoundation::TRUE ? true : false
625
- end
626
-
627
- Class = CoreFoundation::VoidPointer
628
- ID = CoreFoundation::VoidPointer
629
- SEL = CoreFoundation::VoidPointer
630
-
631
- extern :NSSelectorFromString, [CoreFoundation::CFTypeRef], SEL
632
-
633
- extern :objc_getClass, [CoreFoundation::CharPointer], Class
634
- extern :class_getName, [Class], CoreFoundation::CharPointer
635
- end
636
-
637
- XCODE_PATH = Pathname.new(`xcrun xcode-select --print-path`.strip).dirname
638
-
639
- def self.load_xcode_framework(framework)
640
- Fiddle.dlopen(XCODE_PATH.join(framework).to_s)
641
- rescue Fiddle::DLError
642
- nil
643
- end
644
-
645
- # @note The IB frameworks only seem to be necessary on Xcode 7+
646
- #
647
- def self.load_xcode_frameworks
648
- DevToolsCore.silence_stderr do
649
- load_xcode_framework('SharedFrameworks/DVTFoundation.framework/DVTFoundation')
650
- load_xcode_framework('SharedFrameworks/DVTSourceControl.framework/DVTSourceControl')
651
- load_xcode_framework('SharedFrameworks/CSServiceClient.framework/CSServiceClient')
652
- load_xcode_framework('Frameworks/IBFoundation.framework/IBFoundation')
653
- load_xcode_framework('Frameworks/IBAutolayoutFoundation.framework/IBAutolayoutFoundation')
654
- load_xcode_framework('Frameworks/IDEFoundation.framework/IDEFoundation')
655
- load_xcode_framework('PlugIns/Xcode3Core.ideplugin/Contents/MacOS/Xcode3Core')
656
- end
657
- end
658
-
659
- class CFDictionary < NSObject
660
- public
661
-
662
- def initialize(dictionary)
663
- @dictionary = dictionary
664
- end
665
-
666
- def plistDescriptionUTF8Data
667
- selector = 'plistDescriptionUTF8Data'
668
- return nil unless NSObject.respondsToSelector(@dictionary, selector)
669
-
670
- plistDescriptionUTF8Data = CFDictionary.objc_msgSend([])
671
- plistDescriptionUTF8Data.call(
672
- @dictionary,
673
- CoreFoundation.NSSelectorFromString(CoreFoundation.RubyStringToCFString(selector)))
674
- end
675
-
676
- def self.image
677
- @image ||= DevToolsCore.load_xcode_frameworks
678
- end
679
- end
680
-
681
- class NSData < NSObject
682
- public
683
-
684
- def initialize(data)
685
- @data = data
686
- end
687
-
688
- def writeToFileAtomically(path)
689
- selector = 'writeToFile:atomically:'
690
- return false unless NSObject.respondsToSelector(@data, selector)
691
-
692
- writeToFileAtomically = NSData.objc_msgSend([CoreFoundation::VoidPointer, CoreFoundation::Boolean], CoreFoundation::Boolean)
693
- result = writeToFileAtomically.call(
694
- @data,
695
- CoreFoundation.NSSelectorFromString(CoreFoundation.RubyStringToCFString(selector)),
696
- CoreFoundation.RubyStringToCFString(path),
697
- 1)
698
- result == CoreFoundation::TRUE ? true : false
699
- end
700
- end
701
-
702
- class PBXProject < NSObject
703
- public
704
-
705
- def initialize(path)
706
- DevToolsCore.silence_stderr do
707
- CoreFoundation.IDEInitialize(1, CoreFoundation::NULL)
708
-
709
- # The parameter is whether UI must be initialized (which we don't need)
710
- CoreFoundation.XCInitializeCoreIfNeeded(0)
711
- end
712
-
713
- selector = 'projectWithFile:'
714
-
715
- if NSObject.respondsToSelector(PBXProject.objc_class, selector)
716
- projectWithFile = PBXProject.objc_msgSend([CoreFoundation::VoidPointer])
717
- @project = projectWithFile.call(
718
- PBXProject.objc_class,
719
- CoreFoundation.NSSelectorFromString(CoreFoundation.RubyStringToCFString(selector)),
720
- CoreFoundation.RubyStringToCFString(path))
721
- end
722
- end
723
-
724
- def close
725
- selector = 'close'
726
- return unless NSObject.respondsToSelector(@project, selector)
727
-
728
- close = PBXProject.objc_msgSend([], CoreFoundation::Void)
729
- close.call(@project, CoreFoundation.NSSelectorFromString(CoreFoundation.RubyStringToCFString(selector)))
730
- end
731
-
732
- def writeToFileSystemProjectFile
733
- selector = 'writeToFileSystemProjectFile:userFile:checkNeedsRevert:'
734
- return unless NSObject.respondsToSelector(@project, selector)
735
-
736
- writeToFile = PBXProject.objc_msgSend([CoreFoundation::Boolean, CoreFoundation::Boolean, CoreFoundation::Boolean], CoreFoundation::Boolean)
737
- result = writeToFile.call(
738
- @project,
739
- CoreFoundation.NSSelectorFromString(CoreFoundation.RubyStringToCFString(selector)),
740
- 1,
741
- 0,
742
- 1)
743
- result == CoreFoundation::TRUE ? true : false
744
- end
745
-
746
- private
747
-
748
- def self.image
749
- @image ||= DevToolsCore.load_xcode_frameworks
750
- end
751
-
752
- extern :IDEInitialize, [CoreFoundation::Boolean, ID], CoreFoundation::Void
753
- extern :XCInitializeCoreIfNeeded, [CoreFoundation::Boolean], CoreFoundation::Void
754
- end
755
-
756
- # rubocop:enable Style/MethodName
757
- # rubocop:enable Style/VariableName
758
- end