xcodeproj 0.28.2 → 1.0.0.beta.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.
@@ -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