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