Class: Pod::Installer::Analyzer::TargetInspector

Inherits:
Object
  • Object
show all
Defined in:
lib/cocoapods/installer/analyzer/target_inspector.rb

Constant Summary collapse

PLATFORM_INFO_URL =
'https://guides.cocoapods.org/syntax/podfile.html#platform'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(target_definition, installation_root) ⇒ TargetInspector

Initialize a new instance

Parameters:

  • target_definition (TargetDefinition)

    @see #target_definition

  • installation_root (Pathname)

    @see #installation_root


26
27
28
29
# File 'lib/cocoapods/installer/analyzer/target_inspector.rb', line 26

def initialize(target_definition, installation_root)
  @target_definition = target_definition
  @installation_root = installation_root
end

Instance Attribute Details

#installation_rootPathname (readonly)

Returns the root of the CocoaPods installation where the Podfile is located.

Returns:

  • (Pathname)

    the root of the CocoaPods installation where the Podfile is located


16
17
18
# File 'lib/cocoapods/installer/analyzer/target_inspector.rb', line 16

def installation_root
  @installation_root
end

#target_definitionTargetDefinition (readonly)

Returns the target definition to inspect.

Returns:

  • (TargetDefinition)

    the target definition to inspect


11
12
13
# File 'lib/cocoapods/installer/analyzer/target_inspector.rb', line 11

def target_definition
  @target_definition
end

Instance Method Details

#compute_archs(user_targets) ⇒ Array<String> (private)

Computes the architectures relevant for the user's targets.

Parameters:

  • Array<PBXNativeTarget] (Array<PBXNativeTarget] user_targets the user's targets of the project of #target_definition which needs to be integrated)

    user_targets the user's targets of the project of

    target_definition which needs to be integrated

Returns:

  • (Array<String>)

174
175
176
177
178
# File 'lib/cocoapods/installer/analyzer/target_inspector.rb', line 174

def compute_archs(user_targets)
  user_targets.flat_map do |target|
    Array(target.common_resolved_build_setting('ARCHS'))
  end.compact.uniq.sort
end

#compute_build_configurations(user_targets) ⇒ Hash{String=>Symbol} (private)

Returns A hash representing the user build configurations where each key corresponds to the name of a configuration and its value to its type (:debug or :release).

Parameters:

  • Array<PBXNativeTarget] (Array<PBXNativeTarget] user_targets the user's targets of the project of #target_definition which needs to be integrated)

    user_targets the user's targets of the project of

    target_definition which needs to be integrated

Returns:

  • (Hash{String=>Symbol})

    A hash representing the user build configurations where each key corresponds to the name of a configuration and its value to its type (:debug or :release).


120
121
122
123
124
125
126
127
128
# File 'lib/cocoapods/installer/analyzer/target_inspector.rb', line 120

def compute_build_configurations(user_targets)
  if user_targets
    user_targets.flat_map { |t| t.build_configurations.map(&:name) }.each_with_object({}) do |name, hash|
      hash[name] = name == 'Debug' ? :debug : :release
    end.merge(target_definition.build_configurations || {})
  else
    target_definition.build_configurations || {}
  end
end

#compute_platform(user_targets) ⇒ Platform (private)

TODO:

Is assigning the platform to the target definition the best way to go?

Note:

This resolves to the lowest deployment target across the user targets.

Returns The platform of the user's targets.

Parameters:

  • Array<PBXNativeTarget] (Array<PBXNativeTarget] user_targets the user's targets of the project of #target_definition which needs to be integrated)

    user_targets the user's targets of the project of

    target_definition which needs to be integrated

Returns:

  • (Platform)

    The platform of the user's targets


141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/cocoapods/installer/analyzer/target_inspector.rb', line 141

def compute_platform(user_targets)
  return target_definition.platform if target_definition.platform
  name = nil
  deployment_target = nil

  user_targets.each do |target|
    name ||= target.platform_name
    raise Informative, 'Targets with different platforms' unless name == target.platform_name
    if !deployment_target || deployment_target > Version.new(target.deployment_target)
      deployment_target = Version.new(target.deployment_target)
    end
  end

  unless name
    raise Informative,
          "Unable to determine the platform for the `#{target_definition.name}` target."
  end

  UI.warn "Automatically assigning platform `#{Platform.string_name(name)}` with version `#{deployment_target}` " \
    "on target `#{target_definition.name}` because no platform was specified. " \
    "Please specify a platform for this target in your Podfile. See `#{PLATFORM_INFO_URL}`."

  target_definition.set_platform(name, deployment_target)
  Platform.new(name, deployment_target)
end

#compute_project_pathPathname

Returns the path of the user project that the #target_definition should integrate.

Returns:

  • (Pathname)

    the path of the user project.

Raises:

  • If the project is implicit and there are multiple projects.

  • If the path doesn't exits.


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/cocoapods/installer/analyzer/target_inspector.rb', line 62

def compute_project_path
  if target_definition.user_project_path
    path = installation_root + target_definition.user_project_path
    path = "#{path}.xcodeproj" unless File.extname(path) == '.xcodeproj'
    path = Pathname.new(path)
    unless path.exist?
      raise Informative, 'Unable to find the Xcode project ' \
      "`#{path}` for the target `#{target_definition.label}`."
    end
  else
    xcodeprojs = installation_root.children.select { |e| e.fnmatch('*.xcodeproj') }
    if xcodeprojs.size == 1
      path = xcodeprojs.first
    else
      raise Informative, 'Could not automatically select an Xcode project. ' \
        "Specify one in your Podfile like so:\n\n" \
        "    project 'path/to/Project.xcodeproj'\n"
    end
  end
  path
end

#compute_recommends_frameworks(target_definition, native_targets) ⇒ Boolean (private)

Checks if any of the targets for the TargetDefinition computed before by #compute_user_project_targets is recommended to be build as a framework due the presence of Swift source code in any of the source build phases.

Parameters:

  • target_definition (TargetDefinition)

    the target definition

  • native_targets (Array<PBXNativeTarget>)

    the targets which are checked for presence of Swift source code

Returns:

  • (Boolean)

    Whether the user project targets to integrate into uses Swift


193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/cocoapods/installer/analyzer/target_inspector.rb', line 193

def compute_recommends_frameworks(target_definition, native_targets)
  file_predicate = nil
  file_predicate = proc do |file_ref|
    if file_ref.respond_to?(:last_known_file_type)
      file_ref.last_known_file_type == 'sourcecode.swift'
    elsif file_ref.respond_to?(:files)
      file_ref.files.any?(&file_predicate)
    else
      false
    end
  end
  target_definition.platform.supports_dynamic_frameworks? || native_targets.any? do |target|
    target.source_build_phase.files.any? do |build_file|
      file_predicate.call(build_file.file_ref)
    end
  end
end

#compute_results(user_project) ⇒ TargetInspectionResult

Inspect the #target_definition

Returns:

Raises:

  • If no user_project is set


37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/cocoapods/installer/analyzer/target_inspector.rb', line 37

def compute_results(user_project)
  raise ArgumentError, 'Cannot compute results without a user project set' unless user_project

  targets = compute_targets(user_project)
  project_target_uuids = targets.map(&:uuid)
  build_configurations = compute_build_configurations(targets)
  platform = compute_platform(targets)
  archs = compute_archs(targets)
  swift_version = compute_swift_version_from_targets(targets)

  result = TargetInspectionResult.new(target_definition, user_project, project_target_uuids,
                                      build_configurations, platform, archs)
  result.target_definition.swift_version = swift_version
  result
end

#compute_swift_version_from_targets(targets) ⇒ String (private)

Compute the Swift version for the target build configurations. If more than one Swift version is defined for a given target, then it will raise.

Parameters:

  • targets (Array<PBXNativeTarget>)

    the targets that are checked for Swift versions.

Returns:

  • (String)

    the targets Swift version or nil


219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/cocoapods/installer/analyzer/target_inspector.rb', line 219

def compute_swift_version_from_targets(targets)
  versions_to_targets = targets.inject({}) do |memo, target|
    # User project may have an xcconfig that specifies the `SWIFT_VERSION`.
    # Xcodeproj handles that xcconfig either not being set or the file not being present on disk.
    # After the first integration the xcconfig set is most probably
    # the one that was generated from CocoaPods. See https://github.com/CocoaPods/CocoaPods/issues/7731 for
    # more details.
    versions = target.resolved_build_setting('SWIFT_VERSION', true).values
    versions.each do |version|
      memo[version] = [] if memo[version].nil?
      memo[version] << target.name unless memo[version].include? target.name
    end
    memo
  end

  case versions_to_targets.count
  when 0
    nil
  when 1
    versions_to_targets.keys.first
  else
    target_version_pairs = versions_to_targets.map do |version_names, target_names|
      target_names.map { |target_name| [target_name, version_names] }
    end

    sorted_pairs = target_version_pairs.flat_map { |i| i }.sort_by do |target_name, version_name|
      "#{target_name} #{version_name}"
    end

    formatted_output = sorted_pairs.map do |target, version_name|
      "#{target}: Swift #{version_name}"
    end.join("\n")

    raise Informative, "There may only be up to 1 unique SWIFT_VERSION per target. Found target(s) with multiple Swift versions:\n#{formatted_output}"
  end
end

#compute_targets(user_project) ⇒ Array<PBXNativeTarget> (private)

Note:

The method first looks if there is a target specified with the link_with option of the TargetDefinition. Otherwise it looks for the target that has the same name of the target definition. Finally if no target was found the first encountered target is returned (it is assumed to be the one to integrate in simple projects).

Returns a list of the targets from the project of #target_definition that needs to be integrated.

Parameters:

  • user_project (Xcodeproj::Project)

    the user project

Returns:

  • (Array<PBXNativeTarget>)

103
104
105
106
107
108
109
110
111
# File 'lib/cocoapods/installer/analyzer/target_inspector.rb', line 103

def compute_targets(user_project)
  native_targets = user_project.native_targets
  target = native_targets.find { |t| t.name == target_definition.name.to_s }
  unless target
    found = native_targets.map { |t| "`#{t.name}`" }.to_sentence
    raise Informative, "Unable to find a target named `#{target_definition.name}` in project `#{Pathname(user_project.path).basename}`, did find #{found}."
  end
  [target]
end