blob: 520267a06c09a1923adbb934d3c291d7ef952fb5 [file] [log] [blame]
require 'xcodeproj'
require 'active_support/core_ext/string/inflections'
module Pod
# The Pods project.
#
# Model class which provides helpers for working with the Pods project
# through the installation process.
#
class Project < Xcodeproj::Project
# Initialize a new instance
#
# @param [Pathname, String] path @see path
# @param [Bool] skip_initialization
# Whether the project should be initialized from scratch.
# @param [Int] object_version
# Object version to use for serialization, defaults to Xcode 3.2 compatible.
#
def initialize(path, skip_initialization = false,
object_version = Xcodeproj::Constants::DEFAULT_OBJECT_VERSION)
super(path, skip_initialization, object_version)
@support_files_group = new_group('Targets Support Files')
@refs_by_absolute_path = {}
@variant_groups_by_path_and_name = {}
@pods = new_group('Pods')
@development_pods = new_group('Development Pods')
self.symroot = LEGACY_BUILD_ROOT
end
# @return [PBXGroup] The group for the support files of the aggregate
# targets.
#
attr_reader :support_files_group
# @return [PBXGroup] The group for the Pods.
#
attr_reader :pods
# @return [PBXGroup] The group for Development Pods.
#
attr_reader :development_pods
public
# @!group Legacy Xcode build root
#-------------------------------------------------------------------------#
LEGACY_BUILD_ROOT = '${SRCROOT}/../build'
# @param [String] symroot
# The build root that is used when Xcode is configured to not use the
# workspace’s build root. Defaults to `${SRCROOT}/../build`.
#
# @return [void]
#
def symroot=(symroot)
root_object.build_configuration_list.build_configurations.each do |config|
config.build_settings['SYMROOT'] = symroot
end
end
public
# @!group Pod Groups
#-------------------------------------------------------------------------#
# Creates a new group for the Pod with the given name and configures its
# path.
#
# @param [String] pod_name
# The name of the Pod.
#
# @param [#to_s] path
# The path to the root of the Pod.
#
# @param [Bool] development
# Wether the group should be added to the Development Pods group.
#
# @param [Bool] absolute
# Wether the path of the group should be set as absolute.
#
# @return [PBXGroup] The new group.
#
def add_pod_group(pod_name, path, development = false, absolute = false)
raise '[BUG]' if pod_group(pod_name)
parent_group = development ? development_pods : pods
source_tree = absolute ? :absolute : :group
group = parent_group.new_group(pod_name, path, source_tree)
group
end
# @return [Array<PBXGroup>] Returns all the group of the Pods.
#
def pod_groups
pods.children.objects + development_pods.children.objects
end
# Returns the group for the Pod with the given name.
#
# @param [String] pod_name
# The name of the Pod.
#
# @return [PBXGroup] The group.
#
def pod_group(pod_name)
pod_groups.find { |group| group.name == pod_name }
end
# @return [Hash] The names of the specification subgroups by key.
#
SPEC_SUBGROUPS = {
:resources => 'Resources',
:frameworks => 'Frameworks',
:developer => 'Pod',
}
# Returns the group for the specification with the give name creating it if
# needed.
#
# @param [String] spec_name
# The full name of the specification.
#
# @return [PBXGroup] The group.
#
def group_for_spec(spec_name, subgroup_key = nil)
pod_name = Specification.root_name(spec_name)
group = pod_group(pod_name)
raise "[Bug] Unable to locate group for Pod named `#{pod_name}`" unless group
if spec_name != pod_name
subspecs_names = spec_name.gsub(pod_name + '/', '').split('/')
subspecs_names.each do |name|
group = group[name] || group.new_group(name)
end
end
if subgroup_key
subgroup_name = SPEC_SUBGROUPS[subgroup_key]
raise ArgumentError, "Unrecognized subgroup key `#{subgroup_key}`" unless subgroup_name
group = group[subgroup_name] || group.new_group(subgroup_name)
end
group
end
# Returns the support files group for the Pod with the given name.
#
# @param [String] pod_name
# The name of the Pod.
#
# @return [PBXGroup] The group.
#
def pod_support_files_group(pod_name, dir)
group = pod_group(pod_name)
support_files_group = group['Support Files']
unless support_files_group
support_files_group = group.new_group('Support Files', dir)
end
support_files_group
end
public
# @!group File references
#-------------------------------------------------------------------------#
# Adds a file reference to given path as a child of the given group.
#
# @param [Array<Pathname,String>] absolute_path
# The path of the file.
#
# @param [PBXGroup] group
# The group for the new file reference.
#
# @param [Bool] reflect_file_system_structure
# Whether group structure should reflect the file system structure.
# If yes, where needed, intermediate groups are created, similar to
# how mkdir -p operates.
#
# @param [Pathname] base_path
# The base path for newly created groups when reflect_file_system_structure is true.
# If nil, the provided group's real_path is used.
#
# @return [PBXFileReference] The new file reference.
#
def add_file_reference(absolute_path, group, reflect_file_system_structure = false, base_path = nil)
file_path_name = absolute_path.is_a?(Pathname) ? absolute_path : Pathname.new(absolute_path)
group = group_for_path_in_group(file_path_name, group, reflect_file_system_structure, base_path)
if ref = reference_for_path(file_path_name.realpath)
@refs_by_absolute_path[absolute_path.to_s] = ref
ref
else
ref = group.new_file(file_path_name.realpath)
@refs_by_absolute_path[absolute_path.to_s] = ref
end
end
# Returns the file reference for the given absolute path.
#
# @param [#to_s] absolute_path
# The absolute path of the file whose reference is needed.
#
# @return [PBXFileReference] The file reference.
# @return [Nil] If no file reference could be found.
#
def reference_for_path(absolute_path)
unless Pathname.new(absolute_path).absolute?
raise ArgumentError, "Paths must be absolute #{absolute_path}"
end
refs_by_absolute_path[absolute_path.to_s]
end
# Adds a file reference to the Podfile.
#
# @param [#to_s] podfile_path
# The path of the Podfile.
#
# @return [PBXFileReference] The new file reference.
#
def add_podfile(podfile_path)
new_file(podfile_path, :project).tap do |podfile_ref|
podfile_ref.xc_language_specification_identifier = 'xcode.lang.ruby'
podfile_ref.explicit_file_type = 'text.script.ruby'
podfile_ref.last_known_file_type = 'text'
end
end
# Adds a new build configuration to the project and populates it with
# default settings according to the provided type.
#
# @note This method extends the original Xcodeproj implementation to
# include a preprocessor definition named after the build
# setting. This is done to support the TargetEnvironmentHeader
# specification of Pods available only on certain build
# configurations.
#
# @param [String] name
# The name of the build configuration.
#
# @param [Symbol] type
# The type of the build configuration used to populate the build
# settings, must be :debug or :release.
#
# @return [XCBuildConfiguration] The new build configuration.
#
def add_build_configuration(name, type)
build_configuration = super
settings = build_configuration.build_settings
definitions = settings['GCC_PREPROCESSOR_DEFINITIONS'] || ['$(inherited)']
defines = [defininition_for_build_configuration(name)]
defines << 'DEBUG' if type == :debug
defines.each do |define|
value = "#{define}=1"
unless definitions.include?(value)
definitions.unshift(value)
end
end
settings['GCC_PREPROCESSOR_DEFINITIONS'] = definitions
if type == :debug
settings['SWIFT_ACTIVE_COMPILATION_CONDITIONS'] = 'DEBUG'
end
build_configuration
end
# @param [String] name
# The name of the build configuration.
#
# @return [String] The preprocessor definition to set for the configuration.
#
def defininition_for_build_configuration(name)
"POD_CONFIGURATION_#{name.underscore}".gsub(/[^a-zA-Z0-9_]/, '_').upcase
end
private
# @!group Private helpers
#-------------------------------------------------------------------------#
# @return [Hash{String => PBXFileReference}] The file references grouped
# by absolute path.
#
attr_reader :refs_by_absolute_path
# @return [Hash{[Pathname, String] => PBXVariantGroup}] The variant groups
# grouped by absolute path of parent dir and name.
#
attr_reader :variant_groups_by_path_and_name
# Returns the group for an absolute file path in another group.
# Creates subgroups to reflect the file system structure if
# reflect_file_system_structure is set to true.
# Makes a variant group if the path points to a localized file inside a
# *.lproj directory. To support Apple Base Internationalization, the same
# variant group is returned for interface files and strings files with
# the same name.
#
# @param [Pathname] absolute_pathname
# The pathname of the file to get the group for.
#
# @param [PBXGroup] group
# The parent group used as the base of the relative path.
#
# @param [Bool] reflect_file_system_structure
# Whether group structure should reflect the file system structure.
# If yes, where needed, intermediate groups are created, similar to
# how mkdir -p operates.
#
# @param [Pathname] base_path
# The base path for the newly created group. If nil, the provided group's real_path is used.
#
# @return [PBXGroup] The appropriate group for the filepath.
# Can be PBXVariantGroup, if the file is localized.
#
def group_for_path_in_group(absolute_pathname, group, reflect_file_system_structure, base_path = nil)
unless absolute_pathname.absolute?
raise ArgumentError, "Paths must be absolute #{absolute_pathname}"
end
unless base_path.nil? || base_path.absolute?
raise ArgumentError, "Paths must be absolute #{base_path}"
end
relative_base = base_path.nil? ? group.real_path : base_path.realdirpath
relative_pathname = absolute_pathname.relative_path_from(relative_base)
relative_dir = relative_pathname.dirname
lproj_regex = /\.lproj/i
# Add subgroups for directories, but treat .lproj as a file
if reflect_file_system_structure
path = relative_base
relative_dir.each_filename do |name|
break if name.to_s =~ lproj_regex
next if name == '.'
# Make sure groups have the correct absolute path set, as intermittent
# directories may not be included in the group structure
path += name
group = group[name] || group.new_group(name, path)
end
end
# Turn files inside .lproj directories into a variant group
if relative_dir.basename.to_s =~ lproj_regex
group_name = variant_group_name(absolute_pathname)
lproj_parent_dir = absolute_pathname.dirname.dirname
group = @variant_groups_by_path_and_name[[lproj_parent_dir, group_name]] ||
group.new_variant_group(group_name, lproj_parent_dir)
@variant_groups_by_path_and_name[[lproj_parent_dir, group_name]] ||= group
end
group
end
# Returns the name to be used for a the variant group for a file at a given path.
# The path must be localized (within an *.lproj directory).
#
# @param [Pathname] The localized path to get a variant group name for.
#
# @return [String] The variant group name.
#
def variant_group_name(path)
unless path.to_s.downcase.include?('.lproj/')
raise ArgumentError, 'Only localized resources can be added to variant groups.'
end
# When using Base Internationalization for XIBs and Storyboards a strings
# file is generated with the same name as the XIB/Storyboard in each .lproj
# directory:
# Base.lproj/MyViewController.xib
# fr.lproj/MyViewController.strings
#
# In this scenario we want the variant group to be the same as the XIB or Storyboard.
#
# Base Internationalization: https://developer.apple.com/library/ios/documentation/MacOSX/Conceptual/BPInternational/InternationalizingYourUserInterface/InternationalizingYourUserInterface.html
if path.extname.downcase == '.strings'
%w(.xib .storyboard).each do |extension|
possible_interface_file = path.dirname.dirname + 'Base.lproj' + path.basename.sub_ext(extension)
return possible_interface_file.basename.to_s if possible_interface_file.exist?
end
end
path.basename.to_s
end
#-------------------------------------------------------------------------#
end
end