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.
- checksums.yaml +4 -4
- data/lib/xcodeproj.rb +1 -1
- data/lib/xcodeproj/command/config_dump.rb +1 -1
- data/lib/xcodeproj/config.rb +14 -5
- data/lib/xcodeproj/config/other_linker_flags_parser.rb +5 -2
- data/lib/xcodeproj/constants.rb +57 -24
- data/lib/xcodeproj/differ.rb +2 -2
- data/lib/xcodeproj/gem_version.rb +1 -1
- data/lib/xcodeproj/plist.rb +89 -0
- data/lib/xcodeproj/plist/ffi.rb +119 -0
- data/lib/xcodeproj/plist/ffi/core_foundation.rb +441 -0
- data/lib/xcodeproj/plist/ffi/dev_tools_core.rb +186 -0
- data/lib/xcodeproj/plist/plist_gem.rb +27 -0
- data/lib/xcodeproj/project.rb +36 -12
- data/lib/xcodeproj/project/object.rb +15 -2
- data/lib/xcodeproj/project/object/group.rb +28 -0
- data/lib/xcodeproj/project/object/helpers/file_references_factory.rb +1 -1
- data/lib/xcodeproj/project/object/native_target.rb +26 -7
- data/lib/xcodeproj/project/object_attributes.rb +10 -0
- data/lib/xcodeproj/project/object_dictionary.rb +2 -0
- data/lib/xcodeproj/project/object_list.rb +12 -0
- data/lib/xcodeproj/project/uuid_generator.rb +1 -0
- data/lib/xcodeproj/scheme/build_action.rb +2 -1
- data/lib/xcodeproj/scheme/environment_variables.rb +170 -0
- data/lib/xcodeproj/scheme/launch_action.rb +16 -0
- data/lib/xcodeproj/scheme/test_action.rb +18 -0
- data/lib/xcodeproj/workspace.rb +125 -34
- data/lib/xcodeproj/workspace/group_reference.rb +64 -0
- metadata +19 -13
- data/lib/xcodeproj/plist_helper.rb +0 -758
@@ -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
|
data/lib/xcodeproj/workspace.rb
CHANGED
@@ -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 [
|
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
|
-
|
15
|
-
|
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
|
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')),
|
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
|
-
|
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
|
-
# @
|
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 <<(
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
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 =
|
91
|
-
|
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
|
-
|
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
|
-
|
223
|
+
#{contents.rstrip}
|
150
224
|
</Workspace>
|
151
|
-
|
225
|
+
DOC
|
152
226
|
end
|
153
227
|
|
154
|
-
# @return [String] The XML representation of a file reference.
|
155
228
|
#
|
156
|
-
# @param [
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
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.
|
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-
|
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/
|
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/
|
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.
|
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
|