xcodeproj 0.28.2 → 1.0.0
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/command/config_dump.rb +2 -1
- data/lib/xcodeproj/config/other_linker_flags_parser.rb +5 -2
- data/lib/xcodeproj/config.rb +14 -5
- data/lib/xcodeproj/constants.rb +62 -27
- data/lib/xcodeproj/differ.rb +7 -7
- data/lib/xcodeproj/gem_version.rb +1 -1
- data/lib/xcodeproj/plist/ffi/core_foundation.rb +441 -0
- data/lib/xcodeproj/plist/ffi/dev_tools_core.rb +188 -0
- data/lib/xcodeproj/plist/ffi.rb +119 -0
- data/lib/xcodeproj/plist/plist_gem.rb +27 -0
- data/lib/xcodeproj/plist.rb +89 -0
- data/lib/xcodeproj/project/object/build_configuration.rb +15 -0
- data/lib/xcodeproj/project/object/build_rule.rb +28 -0
- 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.rb +16 -3
- 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 +16 -4
- data/lib/xcodeproj/project/project_helper.rb +1 -1
- data/lib/xcodeproj/project/uuid_generator.rb +1 -0
- data/lib/xcodeproj/project.rb +36 -12
- data/lib/xcodeproj/scheme/build_action.rb +5 -2
- 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/scheme.rb +5 -5
- data/lib/xcodeproj/workspace/file_reference.rb +2 -3
- data/lib/xcodeproj/workspace/group_reference.rb +64 -0
- data/lib/xcodeproj/workspace.rb +127 -34
- data/lib/xcodeproj.rb +1 -1
- metadata +29 -17
- data/lib/xcodeproj/plist_helper.rb +0 -758
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
module Xcodeproj
|
|
2
|
+
module Plist
|
|
3
|
+
# Provides support for loading and serializing property list files via
|
|
4
|
+
# Fiddle and CoreFoundation / Xcode.
|
|
5
|
+
#
|
|
6
|
+
module FFI
|
|
7
|
+
autoload :CoreFoundation, 'xcodeproj/plist/ffi/core_foundation'
|
|
8
|
+
autoload :DevToolsCore, 'xcodeproj/plist/ffi/dev_tools_core'
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
# Attempts to load the `fiddle` and Xcode based plist serializer.
|
|
12
|
+
#
|
|
13
|
+
# @return [String,Nil] The loading error message, or `nil` if loading
|
|
14
|
+
# was successful.
|
|
15
|
+
#
|
|
16
|
+
def attempt_to_load!
|
|
17
|
+
return @attempt_to_load if defined?(@attempt_to_load)
|
|
18
|
+
@attempt_to_load = begin
|
|
19
|
+
require 'fiddle'
|
|
20
|
+
nil
|
|
21
|
+
rescue LoadError
|
|
22
|
+
'Xcodeproj relies on a library called `fiddle` to read and write ' \
|
|
23
|
+
'Xcode project files. Ensure your Ruby installation includes ' \
|
|
24
|
+
'`fiddle` and try again.'
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Serializes a hash as an XML property list file.
|
|
29
|
+
#
|
|
30
|
+
# @param [#to_hash] hash
|
|
31
|
+
# The hash to store.
|
|
32
|
+
#
|
|
33
|
+
# @param [#to_s] path
|
|
34
|
+
# The path of the file.
|
|
35
|
+
#
|
|
36
|
+
def write_to_path(hash, path)
|
|
37
|
+
raise ThreadError, 'Can only write plists from the main thread.' unless Thread.current == Thread.main
|
|
38
|
+
|
|
39
|
+
if DevToolsCore.load_xcode_frameworks && path.end_with?('pbxproj')
|
|
40
|
+
ruby_hash_write_xcode(hash, path)
|
|
41
|
+
else
|
|
42
|
+
CoreFoundation.RubyHashPropertyListWrite(hash, path)
|
|
43
|
+
fix_encoding(path)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @return [Hash] Returns the native objects loaded from a property list
|
|
48
|
+
# file.
|
|
49
|
+
#
|
|
50
|
+
# @param [#to_s] path
|
|
51
|
+
# The path of the file.
|
|
52
|
+
#
|
|
53
|
+
def read_from_path(path)
|
|
54
|
+
CoreFoundation.RubyHashPropertyListRead(path)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Simple workaround to escape characters which are outside of ASCII
|
|
60
|
+
# character-encoding. Relies on the fact that there are no XML characters
|
|
61
|
+
# which would need to be escaped.
|
|
62
|
+
#
|
|
63
|
+
# @note This is necessary because Xcode (4.6 currently) uses the MacRoman
|
|
64
|
+
# encoding unless the `// !$*UTF8*$!` magic comment is present. It
|
|
65
|
+
# is not possible to serialize a plist using the NeXTSTEP format
|
|
66
|
+
# without access to the private classes of Xcode and that comment
|
|
67
|
+
# is not compatible with the XML format. For the complete
|
|
68
|
+
# discussion see CocoaPods/CocoaPods#926.
|
|
69
|
+
#
|
|
70
|
+
#
|
|
71
|
+
# @note Sadly this hack is not sufficient for supporting Emoji.
|
|
72
|
+
#
|
|
73
|
+
# @param [String, Pathname] The path of the file which needs to be fixed.
|
|
74
|
+
#
|
|
75
|
+
# @return [void]
|
|
76
|
+
#
|
|
77
|
+
def fix_encoding(filename)
|
|
78
|
+
output = ''
|
|
79
|
+
input = File.open(filename, 'rb', &:read)
|
|
80
|
+
input.unpack('U*').each do |codepoint|
|
|
81
|
+
if codepoint > 127 # ASCII is 7-bit, so 0-127 are valid characters
|
|
82
|
+
output << "&##{codepoint};"
|
|
83
|
+
else
|
|
84
|
+
output << codepoint.chr
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
File.open(filename, 'wb') { |file| file.write(output) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Serializes a hash as an ASCII plist, using Xcode.
|
|
91
|
+
#
|
|
92
|
+
# @param [Hash] hash
|
|
93
|
+
# The hash to store.
|
|
94
|
+
#
|
|
95
|
+
# @param [String] path
|
|
96
|
+
# The path of the file.
|
|
97
|
+
#
|
|
98
|
+
def ruby_hash_write_xcode(hash, path)
|
|
99
|
+
path = File.expand_path(path)
|
|
100
|
+
success = true
|
|
101
|
+
|
|
102
|
+
begin
|
|
103
|
+
plist = DevToolsCore::CFDictionary.new(CoreFoundation.RubyHashToCFDictionary(hash))
|
|
104
|
+
data = DevToolsCore::NSData.new(plist.plistDescriptionUTF8Data)
|
|
105
|
+
success &= data.writeToFileAtomically(path)
|
|
106
|
+
|
|
107
|
+
project = DevToolsCore::PBXProject.new(path)
|
|
108
|
+
success &= project.writeToFileSystemProjectFile
|
|
109
|
+
project.close
|
|
110
|
+
rescue Fiddle::DLError
|
|
111
|
+
success = false
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
CoreFoundation.RubyHashPropertyListWrite(hash, path) unless success
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Xcodeproj
|
|
2
|
+
module Plist
|
|
3
|
+
# @visibility private
|
|
4
|
+
module PlistGem
|
|
5
|
+
def self.attempt_to_load!
|
|
6
|
+
return @attempt_to_load if defined?(@attempt_to_load)
|
|
7
|
+
@attempt_to_load = begin
|
|
8
|
+
require 'plist/parser'
|
|
9
|
+
require 'plist/generator'
|
|
10
|
+
nil
|
|
11
|
+
rescue LoadError
|
|
12
|
+
'Xcodeproj relies on a library called `plist` to read and write ' \
|
|
13
|
+
'Xcode project files. Ensure you have the `plist` gem installed ' \
|
|
14
|
+
'and try again.'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.write_to_path(hash, path)
|
|
19
|
+
::Plist::Emit.save_plist(hash, path)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.read_from_path(path)
|
|
23
|
+
::Plist.parse_xml(path)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Xcodeproj
|
|
2
|
+
# Provides support for loading and serializing property list files.
|
|
3
|
+
#
|
|
4
|
+
module Plist
|
|
5
|
+
autoload :FFI, 'xcodeproj/plist/ffi'
|
|
6
|
+
autoload :PlistGem, 'xcodeproj/plist/plist_gem'
|
|
7
|
+
|
|
8
|
+
# @return [Hash] Returns the native objects loaded from a property list
|
|
9
|
+
# file.
|
|
10
|
+
#
|
|
11
|
+
# @param [#to_s] path
|
|
12
|
+
# The path of the file.
|
|
13
|
+
#
|
|
14
|
+
def self.read_from_path(path)
|
|
15
|
+
path = path.to_s
|
|
16
|
+
unless File.exist?(path)
|
|
17
|
+
raise Informative, "The plist file at path `#{path}` doesn't exist."
|
|
18
|
+
end
|
|
19
|
+
if file_in_conflict?(path)
|
|
20
|
+
raise Informative, "The file `#{path}` is in a merge conflict."
|
|
21
|
+
end
|
|
22
|
+
implementation.read_from_path(path)
|
|
23
|
+
end
|
|
24
|
+
|
|
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 self.write_to_path(hash, path)
|
|
34
|
+
if hash.respond_to?(:to_hash)
|
|
35
|
+
hash = hash.to_hash
|
|
36
|
+
else
|
|
37
|
+
raise TypeError, "The given `#{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.empty?
|
|
46
|
+
implementation.write_to_path(hash, path)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The known modules that can serialize plists.
|
|
50
|
+
#
|
|
51
|
+
KNOWN_IMPLEMENTATIONS = [:FFI, :PlistGem]
|
|
52
|
+
|
|
53
|
+
class << self
|
|
54
|
+
# @return The module used to implement plist serialization.
|
|
55
|
+
#
|
|
56
|
+
attr_accessor :implementation
|
|
57
|
+
def implementation
|
|
58
|
+
@implementation ||= autoload_implementation
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Attempts to autoload a known plist implementation.
|
|
63
|
+
#
|
|
64
|
+
# @return a successfully loaded plist serialization implementation.
|
|
65
|
+
#
|
|
66
|
+
def self.autoload_implementation
|
|
67
|
+
failures = KNOWN_IMPLEMENTATIONS.map do |impl|
|
|
68
|
+
begin
|
|
69
|
+
impl = Plist.const_get(impl)
|
|
70
|
+
failure = impl.attempt_to_load!
|
|
71
|
+
return impl if failure.nil?
|
|
72
|
+
failure
|
|
73
|
+
rescue NameError, LoadError => e
|
|
74
|
+
e.message
|
|
75
|
+
end
|
|
76
|
+
end.compact
|
|
77
|
+
raise Informative, "Unable to load a plist implementation:\n\n#{failures.join("\n\n")}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [Bool] Checks whether there are merge conflicts in the file.
|
|
81
|
+
#
|
|
82
|
+
# @param [#to_s] path
|
|
83
|
+
# The path of the file.
|
|
84
|
+
#
|
|
85
|
+
def self.file_in_conflict?(path)
|
|
86
|
+
File.read(path).match(/^(<|=|>){7}/)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -47,6 +47,21 @@ module Xcodeproj
|
|
|
47
47
|
self.build_settings = sorted_build_settings
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
# @return [Boolean] Whether this configuration is configured for
|
|
51
|
+
# debugging.
|
|
52
|
+
#
|
|
53
|
+
def debug?
|
|
54
|
+
gcc_preprocessor_definitions = build_settings['GCC_PREPROCESSOR_DEFINITIONS']
|
|
55
|
+
gcc_preprocessor_definitions && gcc_preprocessor_definitions.include?('DEBUG=1')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @return [Symbol] The symbolic type of this configuration, either
|
|
59
|
+
# `:debug` or `:release`.
|
|
60
|
+
#
|
|
61
|
+
def type
|
|
62
|
+
debug? ? :debug : :release
|
|
63
|
+
end
|
|
64
|
+
|
|
50
65
|
#---------------------------------------------------------------------#
|
|
51
66
|
|
|
52
67
|
private
|
|
@@ -46,12 +46,40 @@ module Xcodeproj
|
|
|
46
46
|
#
|
|
47
47
|
attribute :output_files, Array
|
|
48
48
|
|
|
49
|
+
# @return [ObjectList<String>] the compiler flags used when creating the
|
|
50
|
+
# respective output files.
|
|
51
|
+
#
|
|
52
|
+
attribute :output_files_compiler_flags, Array
|
|
53
|
+
|
|
49
54
|
# @return [String] the content of the script to use for the build rule.
|
|
50
55
|
#
|
|
51
56
|
# @note This attribute is present if the #{#compiler_spec} is
|
|
52
57
|
# `com.apple.compilers.proxy.script`
|
|
53
58
|
#
|
|
54
59
|
attribute :script, String
|
|
60
|
+
|
|
61
|
+
# @!group Helpers
|
|
62
|
+
|
|
63
|
+
# Adds an output file with the specified compiler flags.
|
|
64
|
+
#
|
|
65
|
+
# @param [PBXFileReference] file the file to add.
|
|
66
|
+
#
|
|
67
|
+
# @param [String] compiler_flags the compiler flags for the file.
|
|
68
|
+
#
|
|
69
|
+
# @return [Void]
|
|
70
|
+
#
|
|
71
|
+
def add_output_file(file, compiler_flags = '')
|
|
72
|
+
(self.output_files ||= []) << file
|
|
73
|
+
(self.output_files_compiler_flags ||= []) << compiler_flags
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @return [Array<[PBXFileReference, String]>]
|
|
77
|
+
# An array containing tuples of output files and their compiler
|
|
78
|
+
# flags.
|
|
79
|
+
#
|
|
80
|
+
def output_files_and_flags
|
|
81
|
+
(output_files || []).zip(output_files_compiler_flags || [])
|
|
82
|
+
end
|
|
55
83
|
end
|
|
56
84
|
end
|
|
57
85
|
end
|
|
@@ -264,6 +264,34 @@ module Xcodeproj
|
|
|
264
264
|
group
|
|
265
265
|
end
|
|
266
266
|
|
|
267
|
+
# Creates a new variant group and adds it to the group
|
|
268
|
+
#
|
|
269
|
+
# @note @see new_group
|
|
270
|
+
#
|
|
271
|
+
# @param [#to_s] name
|
|
272
|
+
# the name of the new group.
|
|
273
|
+
#
|
|
274
|
+
# @param [#to_s] path
|
|
275
|
+
# The, preferably absolute, path of the variant group.
|
|
276
|
+
# Pass the path of the folder containing all the .lproj bundles,
|
|
277
|
+
# that contain files for the variant group.
|
|
278
|
+
# Do not pass the path of a specific bundle (such as en.lproj)
|
|
279
|
+
#
|
|
280
|
+
# @param [Symbol] source_tree
|
|
281
|
+
# The source tree key to use to configure the path (@see
|
|
282
|
+
# GroupableHelper::SOURCE_TREES_BY_KEY).
|
|
283
|
+
#
|
|
284
|
+
# @return [PBXVariantGroup] the new variant group.
|
|
285
|
+
#
|
|
286
|
+
def new_variant_group(name, path = nil, source_tree = :group)
|
|
287
|
+
group = project.new(PBXVariantGroup)
|
|
288
|
+
children << group
|
|
289
|
+
group.name = name
|
|
290
|
+
group.set_source_tree(source_tree)
|
|
291
|
+
group.set_path(path)
|
|
292
|
+
group
|
|
293
|
+
end
|
|
294
|
+
|
|
267
295
|
# Traverses the children groups and finds the group with the given
|
|
268
296
|
# path, if exists.
|
|
269
297
|
#
|
|
@@ -139,7 +139,7 @@ module Xcodeproj
|
|
|
139
139
|
new_file_reference(ref, child_path, :group)
|
|
140
140
|
elsif File.basename(child_path) == '.xccurrentversion'
|
|
141
141
|
full_path = path + File.basename(child_path)
|
|
142
|
-
xccurrentversion =
|
|
142
|
+
xccurrentversion = Plist.read_from_path(full_path)
|
|
143
143
|
current_version_name = xccurrentversion['_XCCurrentVersionName']
|
|
144
144
|
end
|
|
145
145
|
end
|
|
@@ -108,15 +108,34 @@ module Xcodeproj
|
|
|
108
108
|
sdk.scan(/[0-9.]+/).first
|
|
109
109
|
end
|
|
110
110
|
|
|
111
|
+
# @visibility private
|
|
112
|
+
#
|
|
113
|
+
# @return [Hash<Symbol, String>]
|
|
114
|
+
# The name of the setting for the deployment target by platform
|
|
115
|
+
# name.
|
|
116
|
+
#
|
|
117
|
+
DEPLOYMENT_TARGET_SETTING_BY_PLATFORM_NAME = {
|
|
118
|
+
:ios => 'IPHONEOS_DEPLOYMENT_TARGET',
|
|
119
|
+
:osx => 'MACOSX_DEPLOYMENT_TARGET',
|
|
120
|
+
:tvos => 'TVOS_DEPLOYMENT_TARGET',
|
|
121
|
+
:watchos => 'WATCHOS_DEPLOYMENT_TARGET',
|
|
122
|
+
}.freeze
|
|
123
|
+
|
|
111
124
|
# @return [String] the deployment target of the target according to its
|
|
112
125
|
# platform.
|
|
113
126
|
#
|
|
114
127
|
def deployment_target
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
128
|
+
return unless setting = DEPLOYMENT_TARGET_SETTING_BY_PLATFORM_NAME[platform_name]
|
|
129
|
+
common_resolved_build_setting(setting)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# @param [String] deployment_target the deployment target to set for
|
|
133
|
+
# the target according to its platform.
|
|
134
|
+
#
|
|
135
|
+
def deployment_target=(deployment_target)
|
|
136
|
+
return unless setting = DEPLOYMENT_TARGET_SETTING_BY_PLATFORM_NAME[platform_name]
|
|
137
|
+
build_configurations.each do |config|
|
|
138
|
+
config.build_settings[setting] = deployment_target
|
|
120
139
|
end
|
|
121
140
|
end
|
|
122
141
|
|
|
@@ -515,8 +534,8 @@ module Xcodeproj
|
|
|
515
534
|
unless phase_class < AbstractBuildPhase
|
|
516
535
|
raise ArgumentError, "#{phase_class} must be a subclass of #{AbstractBuildPhase.class}"
|
|
517
536
|
end
|
|
518
|
-
@phases[phase_class] ||= build_phases.find { |bp| bp.class == phase_class }
|
|
519
|
-
|
|
537
|
+
@phases[phase_class] ||= build_phases.find { |bp| bp.class == phase_class } ||
|
|
538
|
+
project.new(phase_class).tap { |bp| build_phases << bp }
|
|
520
539
|
end
|
|
521
540
|
|
|
522
541
|
public
|
|
@@ -59,11 +59,12 @@ module Xcodeproj
|
|
|
59
59
|
# @visibility private
|
|
60
60
|
#
|
|
61
61
|
def initialize(project, uuid)
|
|
62
|
-
@project
|
|
62
|
+
@project = project
|
|
63
|
+
@uuid = uuid
|
|
63
64
|
@isa = self.class.isa
|
|
64
65
|
@referrers = []
|
|
65
|
-
unless @isa
|
|
66
|
-
raise
|
|
66
|
+
unless @isa =~ /^(PBX|XC)/
|
|
67
|
+
raise "[Xcodeproj] Attempt to initialize an abstract class (#{self.class})."
|
|
67
68
|
end
|
|
68
69
|
end
|
|
69
70
|
|
|
@@ -98,6 +99,7 @@ module Xcodeproj
|
|
|
98
99
|
# @return [void]
|
|
99
100
|
#
|
|
100
101
|
def remove_from_project
|
|
102
|
+
mark_project_as_dirty!
|
|
101
103
|
project.objects_by_uuid.delete(uuid)
|
|
102
104
|
|
|
103
105
|
referrers.dup.each do |referrer|
|
|
@@ -214,6 +216,7 @@ module Xcodeproj
|
|
|
214
216
|
def remove_referrer(referrer)
|
|
215
217
|
@referrers.delete(referrer)
|
|
216
218
|
if @referrers.count == 0
|
|
219
|
+
mark_project_as_dirty!
|
|
217
220
|
@project.objects_by_uuid.delete(uuid)
|
|
218
221
|
end
|
|
219
222
|
end
|
|
@@ -241,6 +244,16 @@ module Xcodeproj
|
|
|
241
244
|
end
|
|
242
245
|
end
|
|
243
246
|
|
|
247
|
+
# Marks the project that this object belongs to as having been modified.
|
|
248
|
+
#
|
|
249
|
+
# @return [void]
|
|
250
|
+
#
|
|
251
|
+
# @visibility private
|
|
252
|
+
#
|
|
253
|
+
def mark_project_as_dirty!
|
|
254
|
+
project.mark_dirty!
|
|
255
|
+
end
|
|
256
|
+
|
|
244
257
|
#---------------------------------------------------------------------#
|
|
245
258
|
|
|
246
259
|
public
|
|
@@ -316,6 +316,14 @@ module Xcodeproj
|
|
|
316
316
|
define_method("#{attrb.name}=") do |value|
|
|
317
317
|
@simple_attributes_hash ||= {}
|
|
318
318
|
attrb.validate_value(value)
|
|
319
|
+
|
|
320
|
+
existing = @simple_attributes_hash[attrb.plist_name]
|
|
321
|
+
if existing.is_a?(Hash) && value.is_a?(Hash)
|
|
322
|
+
return value if existing.keys == value.keys && existing == value
|
|
323
|
+
elsif existing == value
|
|
324
|
+
return value if existing == value
|
|
325
|
+
end
|
|
326
|
+
mark_project_as_dirty!
|
|
319
327
|
@simple_attributes_hash[attrb.plist_name] = value
|
|
320
328
|
end
|
|
321
329
|
end
|
|
@@ -352,6 +360,8 @@ module Xcodeproj
|
|
|
352
360
|
attrb.validate_value(value)
|
|
353
361
|
|
|
354
362
|
previous_value = send(attrb.name)
|
|
363
|
+
return value if previous_value == value
|
|
364
|
+
mark_project_as_dirty!
|
|
355
365
|
previous_value.remove_referrer(self) if previous_value
|
|
356
366
|
instance_variable_set("@#{attrb.name}", value)
|
|
357
367
|
value.add_referrer(self) if value
|
|
@@ -187,6 +187,7 @@ module Xcodeproj
|
|
|
187
187
|
# @return [void]
|
|
188
188
|
#
|
|
189
189
|
def perform_additions_operations(object, key)
|
|
190
|
+
owner.mark_project_as_dirty!
|
|
190
191
|
object.add_referrer(owner)
|
|
191
192
|
attribute.validate_value_for_key(object, key)
|
|
192
193
|
end
|
|
@@ -197,6 +198,7 @@ module Xcodeproj
|
|
|
197
198
|
# @return [void]
|
|
198
199
|
#
|
|
199
200
|
def perform_deletion_operations(objects)
|
|
201
|
+
owner.mark_project_as_dirty!
|
|
200
202
|
objects.remove_referrer(owner)
|
|
201
203
|
end
|
|
202
204
|
end
|
|
@@ -140,10 +140,10 @@ module Xcodeproj
|
|
|
140
140
|
super
|
|
141
141
|
end
|
|
142
142
|
|
|
143
|
-
# Moves the
|
|
143
|
+
# Moves the given object to the given index.
|
|
144
144
|
#
|
|
145
|
-
# @param [
|
|
146
|
-
# The
|
|
145
|
+
# @param [AbstractObject, ObjectDictionary] object
|
|
146
|
+
# The object to move.
|
|
147
147
|
#
|
|
148
148
|
# @param [Fixnum] to
|
|
149
149
|
# The new index for the object.
|
|
@@ -151,6 +151,7 @@ module Xcodeproj
|
|
|
151
151
|
# @return [void]
|
|
152
152
|
#
|
|
153
153
|
def move(object, new_index)
|
|
154
|
+
return if index(object) == new_index
|
|
154
155
|
if obj = delete(object)
|
|
155
156
|
insert(new_index, obj)
|
|
156
157
|
else
|
|
@@ -158,7 +159,7 @@ module Xcodeproj
|
|
|
158
159
|
end
|
|
159
160
|
end
|
|
160
161
|
|
|
161
|
-
# Moves the object at the given
|
|
162
|
+
# Moves the object at the given index to the given position.
|
|
162
163
|
#
|
|
163
164
|
# @param [Fixnum] from
|
|
164
165
|
# The current index of the object.
|
|
@@ -169,6 +170,7 @@ module Xcodeproj
|
|
|
169
170
|
# @return [void]
|
|
170
171
|
#
|
|
171
172
|
def move_from(current_index, new_index)
|
|
173
|
+
return if current_index == new_index
|
|
172
174
|
if obj = delete_at(current_index)
|
|
173
175
|
insert(new_index, obj)
|
|
174
176
|
else
|
|
@@ -176,6 +178,14 @@ module Xcodeproj
|
|
|
176
178
|
end
|
|
177
179
|
end
|
|
178
180
|
|
|
181
|
+
def sort!
|
|
182
|
+
return super if owner.project.dirty?
|
|
183
|
+
previous = to_a
|
|
184
|
+
super
|
|
185
|
+
owner.mark_project_as_dirty! unless previous == to_a
|
|
186
|
+
self
|
|
187
|
+
end
|
|
188
|
+
|
|
179
189
|
private
|
|
180
190
|
|
|
181
191
|
# @!group Notification Methods
|
|
@@ -190,6 +200,7 @@ module Xcodeproj
|
|
|
190
200
|
def perform_additions_operations(objects)
|
|
191
201
|
objects = [objects] unless objects.is_a?(Array)
|
|
192
202
|
objects.each do |obj|
|
|
203
|
+
owner.mark_project_as_dirty!
|
|
193
204
|
obj.add_referrer(owner)
|
|
194
205
|
attribute.validate_value(obj) unless obj.is_a?(ObjectDictionary)
|
|
195
206
|
end
|
|
@@ -203,6 +214,7 @@ module Xcodeproj
|
|
|
203
214
|
def perform_deletion_operations(objects)
|
|
204
215
|
objects = [objects] unless objects.is_a?(Array)
|
|
205
216
|
objects.each do |obj|
|
|
217
|
+
owner.mark_project_as_dirty!
|
|
206
218
|
obj.remove_referrer(owner) unless obj.is_a?(ObjectDictionary)
|
|
207
219
|
end
|
|
208
220
|
end
|
|
@@ -189,7 +189,7 @@ module Xcodeproj
|
|
|
189
189
|
|
|
190
190
|
new_config = project.new(XCBuildConfiguration)
|
|
191
191
|
new_config.name = configuration.name
|
|
192
|
-
new_config.build_settings = common_build_settings(
|
|
192
|
+
new_config.build_settings = common_build_settings(configuration.type, platform, deployment_target, target_product_type, language)
|
|
193
193
|
cl.build_configurations << new_config
|
|
194
194
|
end
|
|
195
195
|
|
data/lib/xcodeproj/project.rb
CHANGED
|
@@ -4,7 +4,7 @@ require 'securerandom'
|
|
|
4
4
|
require 'xcodeproj/project/object'
|
|
5
5
|
require 'xcodeproj/project/project_helper'
|
|
6
6
|
require 'xcodeproj/project/uuid_generator'
|
|
7
|
-
require 'xcodeproj/
|
|
7
|
+
require 'xcodeproj/plist'
|
|
8
8
|
|
|
9
9
|
module Xcodeproj
|
|
10
10
|
# This class represents a Xcode project document.
|
|
@@ -55,19 +55,26 @@ module Xcodeproj
|
|
|
55
55
|
# @example Creating a project
|
|
56
56
|
# Project.new("path/to/Project.xcodeproj")
|
|
57
57
|
#
|
|
58
|
+
# @note When initializing the project, Xcodeproj mimics the Xcode behaviour
|
|
59
|
+
# including the setup of a debug and release configuration. If you want a
|
|
60
|
+
# clean project without any configurations, you should override the
|
|
61
|
+
# `initialize_from_scratch` method to not add these configurations and
|
|
62
|
+
# manually set the object version.
|
|
63
|
+
#
|
|
58
64
|
def initialize(path, skip_initialization = false, object_version = Constants::DEFAULT_OBJECT_VERSION)
|
|
59
65
|
@path = Pathname.new(path).expand_path
|
|
60
66
|
@objects_by_uuid = {}
|
|
61
67
|
@generated_uuids = []
|
|
62
68
|
@available_uuids = []
|
|
63
|
-
|
|
64
|
-
initialize_from_scratch
|
|
65
|
-
@object_version = object_version.to_s
|
|
66
|
-
end
|
|
69
|
+
@dirty = true
|
|
67
70
|
unless skip_initialization.is_a?(TrueClass) || skip_initialization.is_a?(FalseClass)
|
|
68
71
|
raise ArgumentError, '[Xcodeproj] Initialization parameter expected to ' \
|
|
69
72
|
"be a boolean #{skip_initialization}"
|
|
70
73
|
end
|
|
74
|
+
unless skip_initialization
|
|
75
|
+
initialize_from_scratch
|
|
76
|
+
@object_version = object_version.to_s
|
|
77
|
+
end
|
|
71
78
|
end
|
|
72
79
|
|
|
73
80
|
# Opens the project at the given path.
|
|
@@ -190,12 +197,13 @@ module Xcodeproj
|
|
|
190
197
|
#
|
|
191
198
|
def initialize_from_file
|
|
192
199
|
pbxproj_path = path + 'project.pbxproj'
|
|
193
|
-
plist =
|
|
200
|
+
plist = Plist.read_from_path(pbxproj_path.to_s)
|
|
194
201
|
root_object.remove_referrer(self) if root_object
|
|
195
|
-
@root_object
|
|
196
|
-
@archive_version =
|
|
197
|
-
@object_version =
|
|
198
|
-
@classes =
|
|
202
|
+
@root_object = new_from_plist(plist['rootObject'], plist['objects'], self)
|
|
203
|
+
@archive_version = plist['archiveVersion']
|
|
204
|
+
@object_version = plist['objectVersion']
|
|
205
|
+
@classes = plist['classes']
|
|
206
|
+
@dirty = false
|
|
199
207
|
|
|
200
208
|
unless root_object
|
|
201
209
|
raise "[Xcodeproj] Unable to find a root object in #{pbxproj_path}."
|
|
@@ -318,9 +326,25 @@ module Xcodeproj
|
|
|
318
326
|
#
|
|
319
327
|
def save(save_path = nil)
|
|
320
328
|
save_path ||= path
|
|
329
|
+
@dirty = false if save_path == path
|
|
321
330
|
FileUtils.mkdir_p(save_path)
|
|
322
331
|
file = File.join(save_path, 'project.pbxproj')
|
|
323
|
-
|
|
332
|
+
Plist.write_to_path(to_hash, file)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Marks the project as dirty, that is, modified from what is on disk.
|
|
336
|
+
#
|
|
337
|
+
# @return [void]
|
|
338
|
+
#
|
|
339
|
+
def mark_dirty!
|
|
340
|
+
@dirty = true
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# @return [Boolean] Whether this project has been modified since read from
|
|
344
|
+
# disk or saved.
|
|
345
|
+
#
|
|
346
|
+
def dirty?
|
|
347
|
+
@dirty == true
|
|
324
348
|
end
|
|
325
349
|
|
|
326
350
|
# Replaces all the UUIDs in the project with deterministic MD5 checksums.
|
|
@@ -736,7 +760,7 @@ module Xcodeproj
|
|
|
736
760
|
end
|
|
737
761
|
|
|
738
762
|
xcschememanagement_path = schemes_dir + 'xcschememanagement.plist'
|
|
739
|
-
|
|
763
|
+
Plist.write_to_path(xcschememanagement, xcschememanagement_path)
|
|
740
764
|
end
|
|
741
765
|
|
|
742
766
|
#-------------------------------------------------------------------------#
|
|
@@ -76,9 +76,12 @@ module Xcodeproj
|
|
|
76
76
|
def initialize(target_or_node = nil)
|
|
77
77
|
create_xml_element_with_fallback(target_or_node, 'BuildActionEntry') do
|
|
78
78
|
# Check target type to configure the default entry attributes accordingly
|
|
79
|
-
is_test_target
|
|
79
|
+
is_test_target = false
|
|
80
|
+
is_app_target = false
|
|
80
81
|
if target_or_node && target_or_node.is_a?(::Xcodeproj::Project::Object::PBXNativeTarget)
|
|
81
|
-
test_types = [Constants::PRODUCT_TYPE_UTI[:octest_bundle],
|
|
82
|
+
test_types = [Constants::PRODUCT_TYPE_UTI[:octest_bundle],
|
|
83
|
+
Constants::PRODUCT_TYPE_UTI[:unit_test_bundle],
|
|
84
|
+
Constants::PRODUCT_TYPE_UTI[:ui_test_bundle]]
|
|
82
85
|
app_types = [Constants::PRODUCT_TYPE_UTI[:application]]
|
|
83
86
|
is_test_target = test_types.include?(target_or_node.product_type)
|
|
84
87
|
is_app_target = app_types.include?(target_or_node.product_type)
|