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