xcodeproj 0.1.0 → 0.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,100 @@
1
+ module Xcodeproj
2
+ class Project
3
+ module Object
4
+
5
+ class XCBuildConfiguration < AbstractPBXObject
6
+ COMMON_BUILD_SETTINGS = {
7
+ :all => {
8
+ 'GCC_VERSION' => 'com.apple.compilers.llvm.clang.1_0',
9
+ 'GCC_PRECOMPILE_PREFIX_HEADER' => 'YES',
10
+ 'PRODUCT_NAME' => '$(TARGET_NAME)',
11
+ 'SKIP_INSTALL' => 'YES',
12
+ 'DSTROOT' => '/tmp/xcodeproj.dst',
13
+ 'ALWAYS_SEARCH_USER_PATHS' => 'NO',
14
+ 'GCC_C_LANGUAGE_STANDARD' => 'gnu99',
15
+ 'INSTALL_PATH' => "$(BUILT_PRODUCTS_DIR)",
16
+ 'GCC_WARN_ABOUT_MISSING_PROTOTYPES' => 'YES',
17
+ 'GCC_WARN_ABOUT_RETURN_TYPE' => 'YES',
18
+ 'GCC_WARN_UNUSED_VARIABLE' => 'YES',
19
+ 'OTHER_LDFLAGS' => '',
20
+ 'COPY_PHASE_STRIP' => 'YES',
21
+ }.freeze,
22
+ :debug => {
23
+ 'GCC_DYNAMIC_NO_PIC' => 'NO',
24
+ 'GCC_PREPROCESSOR_DEFINITIONS' => ["DEBUG=1", "$(inherited)"],
25
+ 'GCC_SYMBOLS_PRIVATE_EXTERN' => 'NO',
26
+ 'GCC_OPTIMIZATION_LEVEL' => '0',
27
+ 'COPY_PHASE_STRIP' => 'NO',
28
+ }.freeze,
29
+ :ios => {
30
+ 'ARCHS' => "$(ARCHS_STANDARD_32_BIT)",
31
+ 'IPHONEOS_DEPLOYMENT_TARGET' => '4.3',
32
+ 'PUBLIC_HEADERS_FOLDER_PATH' => "$(TARGET_NAME)",
33
+ 'SDKROOT' => 'iphoneos',
34
+ }.freeze,
35
+ :osx => {
36
+ 'ARCHS' => "$(ARCHS_STANDARD_64_BIT)",
37
+ 'GCC_ENABLE_OBJC_EXCEPTIONS' => 'YES',
38
+ 'GCC_WARN_64_TO_32_BIT_CONVERSION' => 'YES',
39
+ 'GCC_VERSION' => 'com.apple.compilers.llvm.clang.1_0',
40
+ 'MACOSX_DEPLOYMENT_TARGET' => '10.7',
41
+ 'SDKROOT' => 'macosx',
42
+ }.freeze,
43
+ [:osx, :debug] => {
44
+ 'ONLY_ACTIVE_ARCH' => 'YES',
45
+ }.freeze,
46
+ [:osx, :release] => {
47
+ 'DEBUG_INFORMATION_FORMAT' => 'dwarf-with-dsym',
48
+ }.freeze,
49
+ [:ios, :release] => {
50
+ 'VALIDATE_PRODUCT' => 'YES',
51
+ }.freeze,
52
+ }.freeze
53
+
54
+ def self.new_release(project)
55
+ new(project, nil,
56
+ 'name' => 'Release',
57
+ 'buildSettings' => COMMON_BUILD_SETTINGS[:all].dup
58
+ )
59
+ end
60
+
61
+ def self.new_debug(project)
62
+ new(project, nil,
63
+ 'name' => 'Debug',
64
+ 'buildSettings' => COMMON_BUILD_SETTINGS[:all].merge(COMMON_BUILD_SETTINGS[:debug])
65
+ )
66
+ end
67
+
68
+ # [Hash] the build settings used when building a target
69
+ attribute :build_settings
70
+
71
+ # TODO why do I need to specify the uuid here?
72
+ has_one :base_configuration, :uuid => :base_configuration_reference
73
+
74
+ def initialize(*)
75
+ super
76
+ self.build_settings ||= {}
77
+ end
78
+ end
79
+
80
+ class XCConfigurationList < AbstractPBXObject
81
+ attribute :default_configuration_is_visible
82
+ attribute :default_configuration_name
83
+
84
+ has_many :build_configurations
85
+
86
+ def initialize(*)
87
+ super
88
+ self.build_configuration_references ||= []
89
+ end
90
+
91
+ def build_settings(build_configuration_name)
92
+ if config = build_configurations.where(:name => build_configuration_name)
93
+ config.build_settings
94
+ end
95
+ end
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,71 @@
1
+ require 'xcodeproj/project/object/group'
2
+
3
+ module Xcodeproj
4
+ class Project
5
+ module Object
6
+
7
+ # @todo Add a list of all possible file types for `explicit_file_type`
8
+ # and `last_known_file_type`.
9
+ class PBXFileReference < AbstractGroupEntry
10
+ # [String] the path to the file relative to the source tree
11
+ attribute :path
12
+
13
+ # [String] the source tree to which the file is relative. It can be one
14
+ # of `SOURCE_ROOT` or `SDKROOT`
15
+ attribute :source_tree
16
+
17
+ # [String] the file type regardless of what Xcode might think it is
18
+ attribute :explicit_file_type
19
+
20
+ # [String] the file type guessed by Xcode
21
+ attribute :last_known_file_type
22
+
23
+ # [String] wether of not this file should be indexed. This can be
24
+ # either "0" or "1".
25
+ attribute :include_in_index
26
+
27
+ has_many :build_files, :inverse_of => :file
28
+
29
+ def self.new_static_library(project, product_name)
30
+ new(project, nil,
31
+ "includeInIndex" => "0",
32
+ "sourceTree" => "BUILT_PRODUCTS_DIR",
33
+ "path" => "lib#{product_name}.a"
34
+ )
35
+ end
36
+
37
+ def initialize(*)
38
+ super
39
+ self.path = path if path # sets default name
40
+ self.source_tree ||= 'SOURCE_ROOT'
41
+ self.include_in_index ||= "1"
42
+ set_default_file_type!
43
+ end
44
+
45
+ alias_method :_path=, :path=
46
+ def path=(path)
47
+ self._path = path
48
+ self.name ||= pathname.basename.to_s
49
+ path
50
+ end
51
+
52
+ def pathname
53
+ Pathname.new(path)
54
+ end
55
+
56
+ def set_default_file_type!
57
+ return if explicit_file_type || last_known_file_type
58
+ case path
59
+ when /\.a$/
60
+ self.explicit_file_type = 'archive.ar'
61
+ when /\.framework$/
62
+ self.last_known_file_type = 'wrapper.framework'
63
+ when /\.xcconfig$/
64
+ self.last_known_file_type = 'text.xcconfig'
65
+ end
66
+ end
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,102 @@
1
+ module Xcodeproj
2
+ class Project
3
+ module Object
4
+
5
+ class AbstractGroupEntry < AbstractPBXObject
6
+ has_one :group, :inverse_of => :children
7
+
8
+ def initialize(project, uuid, attributes)
9
+ is_new = uuid.nil?
10
+ super
11
+ # If there's no root_object yet, then this is probably the main group.
12
+ if is_new && @project.root_object
13
+ @project.main_group.children << self
14
+ end
15
+ end
16
+
17
+ def destroy
18
+ group.child_references.delete(uuid)
19
+ super
20
+ end
21
+
22
+ # Sorts groups before files and inside those sorts by name.
23
+ def <=>(other)
24
+ if self.is_a?(PBXGroup) && other.is_a?(PBXFileReference)
25
+ -1
26
+ elsif self.is_a?(PBXFileReference) && other.is_a?(PBXGroup)
27
+ 1
28
+ else
29
+ self.name <=> other.name
30
+ end
31
+ end
32
+ end
33
+
34
+ # @todo The `source_tree` can probably be more than just `<group>`.
35
+ class PBXGroup < AbstractGroupEntry
36
+ # [String] the source tree to which this group is relative. It can be
37
+ # `<group>`.
38
+ attribute :source_tree
39
+
40
+ has_many :children, :class => AbstractGroupEntry do |child|
41
+ # Associating the AbstractGroupEntry instance to this group through
42
+ # the inverse association will also remove it from the group it was
43
+ # in.
44
+ child.group = self
45
+ end
46
+
47
+ def initialize(*)
48
+ super
49
+ self.source_tree ||= '<group>'
50
+ self.child_references ||= []
51
+ end
52
+
53
+ def main_group?
54
+ @project.main_group.uuid == uuid
55
+ end
56
+
57
+ def name
58
+ if name = super
59
+ name
60
+ elsif attributes.has_key?('path')
61
+ File.basename(attributes['path'])
62
+ elsif main_group?
63
+ 'Main Group'
64
+ end
65
+ end
66
+
67
+ def files
68
+ children.list_by_class(PBXFileReference)
69
+ end
70
+
71
+ def create_file(path)
72
+ files.new("path" => path)
73
+ end
74
+
75
+ def create_files(paths)
76
+ paths.map { |path| create_file(path) }
77
+ end
78
+
79
+ def source_files
80
+ children.list_by_class(PBXFileReference) do |list|
81
+ list.let(:uuid_scope) do
82
+ files.reject { |file| file.build_files.empty? }.map(&:uuid)
83
+ end
84
+ end
85
+ end
86
+
87
+ def groups
88
+ children.list_by_class(PBXGroup)
89
+ end
90
+
91
+ def create_group(name)
92
+ groups.new("name" => name)
93
+ end
94
+
95
+ def <<(child)
96
+ children << child
97
+ end
98
+ end
99
+
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,126 @@
1
+ module Xcodeproj
2
+ class Project
3
+ module Object
4
+
5
+ class PBXNativeTarget < AbstractPBXObject
6
+ STATIC_LIBRARY = 'com.apple.product-type.library.static'
7
+
8
+ # [String] the name of the build product
9
+ attribute :product_name
10
+
11
+ # [String] the build product type identifier
12
+ attribute :product_type
13
+
14
+ has_many :build_phases
15
+ has_many :dependencies # TODO :class => ?
16
+ has_many :build_rules # TODO :class => ?
17
+ has_one :build_configuration_list, :class => XCConfigurationList
18
+ has_one :product, :uuid => :product_reference
19
+
20
+ def self.new_static_library(project, platform, name)
21
+ project.add_system_framework(platform == :ios ? 'Foundation' : 'Cocoa')
22
+
23
+ target = new(project, nil, 'productType' => STATIC_LIBRARY, 'productName' => name)
24
+ target.product.path = "lib#{name}.a"
25
+
26
+ target.build_configurations.each do |config|
27
+ config.build_settings.merge!(XCBuildConfiguration::COMMON_BUILD_SETTINGS[platform])
28
+
29
+ # E.g. [:ios, :release]
30
+ extra_settings_key = [platform, config.name.downcase.to_sym]
31
+ if extra_settings = XCBuildConfiguration::COMMON_BUILD_SETTINGS[extra_settings_key]
32
+ config.build_settings.merge!(extra_settings)
33
+ end
34
+ end
35
+
36
+ target
37
+ end
38
+
39
+ # You need to specify a product. For a static library you can use
40
+ # PBXFileReference.new_static_library.
41
+ def initialize(project, *)
42
+ super
43
+ self.name ||= product_name
44
+ self.build_rule_references ||= []
45
+ self.dependency_references ||= []
46
+
47
+ unless build_phase_references
48
+ self.build_phase_references = []
49
+
50
+ source_build_phases.new
51
+ copy_files_build_phases.new
52
+ #shell_script_build_phases.new
53
+
54
+ phase = frameworks_build_phases.new
55
+ if frameworks_group = @project.groups.where(:name => 'Frameworks')
56
+ frameworks_group.files.each { |framework| phase << framework }
57
+ end
58
+ end
59
+
60
+ unless build_configuration_list
61
+ self.build_configuration_list = project.objects.add(XCConfigurationList, {
62
+ 'defaultConfigurationIsVisible' => '0',
63
+ 'defaultConfigurationName' => 'Release',
64
+ })
65
+ # TODO or should this happen in buildConfigurationList?
66
+ build_configuration_list.build_configurations.new_debug
67
+ build_configuration_list.build_configurations.new_release
68
+ end
69
+
70
+ unless product
71
+ self.product = @project.files.new_static_library(product_name)
72
+ end
73
+ end
74
+
75
+ alias_method :_product=, :product=
76
+ def product=(product)
77
+ self._product = product
78
+ @project.products << product
79
+ end
80
+
81
+ def build_configurations
82
+ build_configuration_list.build_configurations
83
+ end
84
+
85
+ def build_settings(build_configuration_name)
86
+ build_configuration_list.build_settings(build_configuration_name)
87
+ end
88
+
89
+ def source_build_phases
90
+ build_phases.list_by_class(PBXSourcesBuildPhase)
91
+ end
92
+
93
+ def copy_files_build_phases
94
+ build_phases.list_by_class(PBXCopyFilesBuildPhase)
95
+ end
96
+
97
+ def frameworks_build_phases
98
+ build_phases.list_by_class(PBXFrameworksBuildPhase)
99
+ end
100
+
101
+ def shell_script_build_phases
102
+ build_phases.list_by_class(PBXShellScriptBuildPhase)
103
+ end
104
+
105
+ # Finds an existing file reference or creates a new one.
106
+ def add_source_file(path, copy_header_phase = nil, compiler_flags = nil)
107
+ file = @project.files.find { |file| file.path == path.to_s } || @project.files.new('path' => path.to_s)
108
+ build_file = file.build_files.new
109
+ if path.extname == '.h'
110
+ build_file.settings = { 'ATTRIBUTES' => ["Public"] }
111
+ # Working around a bug in Xcode 4.2 betas, remove this once the Xcode bug is fixed:
112
+ # https://github.com/alloy/cocoapods/issues/13
113
+ #phase = copy_header_phase || headers_build_phases.first
114
+ phase = copy_header_phase || copy_files_build_phases.first
115
+ phase.build_files << build_file
116
+ else
117
+ build_file.settings = { 'COMPILER_FLAGS' => compiler_flags } if compiler_flags
118
+ source_build_phases.first.build_files << build_file
119
+ end
120
+ file
121
+ end
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,146 @@
1
+ module Xcodeproj
2
+ class Project
3
+
4
+ # In case `scoped` is an Array the list's order is maintained.
5
+ class PBXObjectList
6
+ include Enumerable
7
+
8
+ def initialize(represented_class, project)
9
+ @represented_class = represented_class
10
+ @project = project
11
+ @callbacks = {}
12
+
13
+ yield self if block_given?
14
+ end
15
+
16
+ # Specify callbacks for:
17
+ # * :uuid_scope Returns the list of UUIDs to scope this list to.
18
+ # * :push When an object is added to the list.
19
+ def let(callback_name, &block)
20
+ raise ArgumentError, "Incorrect callback `#{callback_name}'." unless [:uuid_scope, :push].include?(callback_name)
21
+ @callbacks[callback_name] = block
22
+ end
23
+
24
+ def uuid_scope
25
+ @callbacks[:uuid_scope].call
26
+ end
27
+
28
+ def empty?
29
+ uuid_scope.empty?
30
+ end
31
+
32
+ def [](uuid)
33
+ if uuid_scope.include?(uuid) && hash = @project.objects_hash[uuid]
34
+ Object.const_get(hash['isa']).new(@project, uuid, hash)
35
+ end
36
+ end
37
+
38
+ def add(klass, hash = {})
39
+ object = klass.new(@project, nil, hash)
40
+ self << object
41
+ object
42
+ end
43
+
44
+ def new(hash = {})
45
+ add(@represented_class, hash)
46
+ end
47
+
48
+ # Run Ruby in debug mode to receive warnings about calls to :push when a
49
+ # list does not have a callback registered for :push.
50
+ def push(object)
51
+ if @callbacks[:push]
52
+ @callbacks[:push].call(object)
53
+ else
54
+ if $DEBUG
55
+ warn "Pushed object onto a PBXObjectList that does not have a :push callback from: #{caller.first}"
56
+ end
57
+ end
58
+ self
59
+ end
60
+ alias_method :<<, :push
61
+
62
+ def each
63
+ uuid_scope.each do |uuid|
64
+ yield self[uuid]
65
+ end
66
+ end
67
+
68
+ def ==(other)
69
+ self.to_a == other.to_a
70
+ end
71
+
72
+ def size
73
+ uuid_scope.size
74
+ end
75
+
76
+ # Since order can't always be guaranteed, these might need to move to an order specific subclass.
77
+ def first
78
+ to_a.first
79
+ end
80
+ def last
81
+ to_a.last
82
+ end
83
+
84
+ def inspect
85
+ "<PBXObjectList: #{map(&:inspect).join(', ')}>"
86
+ end
87
+
88
+ def where(attributes)
89
+ find { |o| o.matches_attributes?(attributes) }
90
+ end
91
+
92
+ # @todo is it really necessary to have an extra method for this?
93
+ def object_named(name)
94
+ where :name => name
95
+ end
96
+
97
+ # Returns a PBXObjectList instance of objects in the list.
98
+ #
99
+ # By default this list will scope the list by objects matching the
100
+ # specified class and add objects, pushed onto the list, to the parent
101
+ # list
102
+ #
103
+ # If a block is given the list instance is yielded so that the default
104
+ # callbacks can be overridden.
105
+ #
106
+ # @param [AbstractPBXObject] klass The AbstractPBXObject subclass to
107
+ # which the list should be scoped.
108
+ #
109
+ # @yield [PBXObjectList] The list instance, allowing you to
110
+ # easily override the callbacks.
111
+ #
112
+ # @return [PBXObjectList<klass>] The list of matching objects.
113
+ def list_by_class(klass)
114
+ parent = self
115
+ PBXObjectList.new(klass, @project) do |list|
116
+ list.let(:push) do |object|
117
+ # Objects added to the subselection should still use the same
118
+ # callback as this list.
119
+ parent << object
120
+ end
121
+ list.let(:uuid_scope) do
122
+ parent.uuid_scope.select do |uuid|
123
+ @project.objects_hash[uuid]['isa'] == klass.isa
124
+ end
125
+ end
126
+ yield list if block_given?
127
+ end
128
+ end
129
+
130
+ # This only makes sense on those with a specific represented class. Not the main objects list.
131
+ def method_missing(name, *args, &block)
132
+ if @represented_class.respond_to?(name)
133
+ object = @represented_class.send(name, @project, *args)
134
+ # The callbacks are only for AbstractPBXObject instances instantiated
135
+ # from the class method that we forwarded the message to.
136
+ self << object if object.is_a?(Object::AbstractPBXObject)
137
+ object
138
+ else
139
+ super
140
+ end
141
+ end
142
+
143
+ end
144
+
145
+ end
146
+ end