xcodeproj 0.1.0 → 0.2.0.rc1

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