Class: Pod::Command::Generator

Inherits:
Pod::Command show all
Defined in:
lib/cocoapods-generator/command/generator.rb

Overview

This is an example of a cocoapods plugin adding a top-level subcommand to the ‘pod’ command.

You can also create subcommands of existing or new commands. Say you wanted to add a subcommand to ‘list` to show newly deprecated pods, (e.g. `pod list deprecated`), there are a few things that would need to change.

  • move this file to ‘lib/pod/command/list/deprecated.rb` and update the class to exist in the the Pod::Command::List namespace

  • change this class to extend from ‘List` instead of `Command`. This tells the plugin system that it is a subcommand of `list`.

  • edit ‘lib/cocoapods_plugins.rb` to require this file

Constant Summary collapse

SPEC_SUBGROUPS =
{
  :resources  => 'Resources',
  :frameworks => 'Frameworks',
}
ENABLE_OBJECT_USE_OBJC_FROM =
{
  :ios => Version.new('6'),
  :osx => Version.new('10.8'),
  :watchos => Version.new('2.0'),
  :tvos => Version.new('9.0'),
}
SOURCE_FILE_EXTENSIONS =
Sandbox::FileAccessor::SOURCE_FILE_EXTENSIONS

Instance Method Summary collapse

Constructor Details

#initialize(argv) ⇒ Generator

Returns a new instance of Generator.



46
47
48
49
50
51
# File 'lib/cocoapods-generator/command/generator.rb', line 46

def initialize(argv)
  @spec_name = argv.shift_argument
  @current_path = Dir.pwd
  @spec_path = @current_path + '/' + @spec_name if @current_path && @spec_name
  super
end

Instance Method Details

#add_file_accessors_paths_to_group(file_accessor_key, group_key = nil) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/cocoapods-generator/command/generator.rb', line 183

def add_file_accessors_paths_to_group(file_accessor_key, group_key = nil)
  @file_accessors.each do |file_accessor|
    pod_name = file_accessor.spec.name
    paths = file_accessor.send(file_accessor_key)
    paths = allowable_project_paths(paths)
    paths.each do |path|
      if !@app_project.reference_for_path(path)
        relative_pathname = path.relative_path_from(Pathname.new(@current_path))
        relative_dir = relative_pathname.dirname
        lproj_regex = /\.lproj/i
        group = group_for_spec(file_accessor.spec.name, group_key)
        relative_dir.each_filename do|name|
          break if name.to_s =~ lproj_regex
          next if name == '.'
          group = group[name] || group.new_group(name)
        end

        file_path_name = path.is_a?(Pathname) ? path : Pathname.new(path)
        ref = group.new_file(file_path_name.realpath)
      end
    end
  end
end

#add_files_to_build_phasesObject



258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/cocoapods-generator/command/generator.rb', line 258

def add_files_to_build_phases
  @file_accessors.each do |file_accessor|
    consumer = file_accessor.spec_consumer

    headers = file_accessor.headers
    public_headers = file_accessor.public_headers
    private_headers = file_accessor.private_headers
    other_source_files = file_accessor.source_files.reject { |sf| SOURCE_FILE_EXTENSIONS.include?(sf.extname) }

    {
      true => file_accessor.arc_source_files,
      false => file_accessor.non_arc_source_files,
    }.each do |arc, files|
      files = files - headers - other_source_files
      flags = compiler_flags_for_consumer(consumer, arc)
      regular_file_refs = files.map { |sf| @app_project.reference_for_path(sf) }
      @framework_target.add_file_references(regular_file_refs, flags)
    end

    header_file_refs = headers.map { |sf| @app_project.reference_for_path(sf) }
    @framework_target.add_file_references(header_file_refs) do |build_file|
      add_header(build_file, public_headers, private_headers)
    end

    other_file_refs = other_source_files.map { |sf| @app_project.reference_for_path(sf) }
    @framework_target.add_file_references(other_file_refs, nil)

    resource_refs = file_accessor.resources.flatten.map do |res|
      @app_project.reference_for_path(res)
    end

    # Some nested files are not directly present in the Xcode project, such as the contents
    # of an .xcdatamodeld directory. These files will return nil file references.
    resource_refs.compact!

    @framework_target.add_resources(resource_refs)
  end
end

#add_framework_target_to_XcodeprojectObject



122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/cocoapods-generator/command/generator.rb', line 122

def add_framework_target_to_Xcodeproject
  project_path = xcodeproj_path
  podspec_consumer = consumer
  platform_name = consumer.platform_name
  deployment_target = podspec_consumer.spec.deployment platform_name
  target_name = @spec_name
  app_project = xcodeproj::Project.open project_path
  app_project.new_target('static_framework', target_name, platform_name, deployment_target)
  app_project.save
  app_project.recreate_user_schemes
  Xcodeproj::XCScheme.share_scheme(app_project.path, target_name)
end

#add_frameworks_bundlesObject



170
171
172
# File 'lib/cocoapods-generator/command/generator.rb', line 170

def add_frameworks_bundles
  add_file_accessors_paths_to_group(:vendored_frameworks, :frameworks)
end

#add_header(build_file, public_headers, private_headers) ⇒ Object



318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/cocoapods-generator/command/generator.rb', line 318

def add_header(build_file, public_headers, private_headers)
  file_ref = build_file.file_ref
  acl = if public_headers.include?(file_ref.real_path)
          'Public'
        elsif private_headers.include?(file_ref.real_path)
          'Private'
        else
          'Project'
        end

  if header_mappings_dir && acl != 'Project'
    relative_path = file_ref.real_path.relative_path_from(header_mappings_dir)
    sub_dir = relative_path.dirname
    copy_phase_name = "Copy #{sub_dir} #{acl} Headers"
    copy_phase = native_target.copy_files_build_phases.find { |bp| bp.name == copy_phase_name } ||
      native_target.new_copy_files_build_phase(copy_phase_name)
    copy_phase.symbol_dst_subfolder_spec = :products_directory
    copy_phase.dst_path = "$(#{acl.upcase}_HEADERS_FOLDER_PATH)/#{sub_dir}"
    copy_phase.add_file_reference(file_ref, true)
  else
    build_file.settings ||= {}
    build_file.settings['ATTRIBUTES'] = [acl]
  end
end

#add_libraries_to_build_phasesObject



297
298
299
300
301
302
303
304
# File 'lib/cocoapods-generator/command/generator.rb', line 297

def add_libraries_to_build_phases
  file_accessor = @file_accessors.first
  @framework_target.add_system_framework(file_accessor.spec_consumer.frameworks)
  @framework_target.add_system_library(file_accessor.spec_consumer.libraries)

  add_vendored_library_to_build_phases(:vendored_frameworks)
  add_vendored_library_to_build_phases(:vendored_libraries)
end

#add_resourcesObject



178
179
180
181
# File 'lib/cocoapods-generator/command/generator.rb', line 178

def add_resources
  add_file_accessors_paths_to_group(:resources, :resources)
  add_file_accessors_paths_to_group(:resource_bundle_files, :resources)
end

#add_source_files_referencesObject



166
167
168
# File 'lib/cocoapods-generator/command/generator.rb', line 166

def add_source_files_references
  add_file_accessors_paths_to_group(:source_files)
end

#add_vendored_librariesObject



174
175
176
# File 'lib/cocoapods-generator/command/generator.rb', line 174

def add_vendored_libraries
  add_file_accessors_paths_to_group(:vendored_libraries, :frameworks)
end

#add_vendored_library_to_build_phases(sourcekey) ⇒ Object



306
307
308
309
310
311
312
313
314
315
316
# File 'lib/cocoapods-generator/command/generator.rb', line 306

def add_vendored_library_to_build_phases(sourcekey)
  file_accessor = @file_accessors.first
  file_accessor.send(sourcekey).each do |path|
    ref = @app_project.reference_for_path(path)
    if ref
      @framework_target.frameworks_build_phase.add_file_reference(ref)
    else
      help! "#{path.basename} no added to project!!"
    end
  end
end

#allowable_project_paths(paths) ⇒ Object



217
218
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
255
256
# File 'lib/cocoapods-generator/command/generator.rb', line 217

def allowable_project_paths(paths)
  lproj_paths = Set.new
  lproj_paths_with_files = Set.new
  allowable_paths = paths.select do |path|
    path_str = path.to_s

    # We add the directory for a Core Data model, but not the items in it.
    next if path_str =~ /.*\.xcdatamodeld\/.+/i

    # We add the directory for a Core Data migration mapping, but not the items in it.
    next if path_str =~ /.*\.xcmappingmodel\/.+/i

    # We add the directory for an asset catalog, but not the items in it.
    next if path_str =~ /.*\.xcassets\/.+/i

    if path_str =~ /\.lproj(\/|$)/i
      # If the element is an .lproj directory then save it and potentially
      # add it later if we don't find any contained items.
      if path_str =~ /\.lproj$/i && path.directory?
        lproj_paths << path
        next
      end

      # Collect the paths for the .lproj directories that contain files.
      lproj_path = /(^.*\.lproj)\/.*/i.match(path_str)[1]
      lproj_paths_with_files << Pathname(lproj_path)

      # Directories nested within an .lproj directory are added as file
      # system references so their contained items are not added directly.
      next if path.dirname.dirname == lproj_path
    end

    true
  end

  # Only add the path for the .lproj directories that do not have anything
  # within them added as well. This generally happens if the glob within the
  # resources directory was not a recursive glob.
  allowable_paths + lproj_paths.subtract(lproj_paths_with_files).to_a
end

#compiler_flags_for_consumer(consumer, arc) ⇒ Object



343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/cocoapods-generator/command/generator.rb', line 343

def compiler_flags_for_consumer(consumer, arc)
  flags = consumer.compiler_flags.dup
  if !arc
    flags << '-fno-objc-arc'
  else
    platform_name = consumer.platform_name
    spec_deployment_target = consumer.spec.deployment_target(platform_name)
    if spec_deployment_target.nil? || Version.new(spec_deployment_target) < ENABLE_OBJECT_USE_OBJC_FROM[platform_name]
      flags << '-DOS_OBJECT_USE_OBJC=0'
    end
  end

  flags * ' '
end

#create_file_accessorsObject



152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/cocoapods-generator/command/generator.rb', line 152

def create_file_accessors
  [@framework_target].each do |target|

    path_list = Sandbox::PathList.new(Pathname.new(Dir.new(@current_path)))
    specs = [@spec_content]
    specs.concat @spec_content.subspecs
    platform = Platform.new(target.platform_name, target.deployment_target)
    @file_accessors = specs.map do |spec|
      file_accessor = Sandbox::FileAccessor.new(path_list, spec.consumer(platform))
      file_accessor
    end
  end
end

#create_spec_contentObject



109
110
111
# File 'lib/cocoapods-generator/command/generator.rb', line 109

def create_spec_content
  @spec_content = Specification::from_file @spec_path
end

#group_for_spec(spec_name, subgroup_key = nil) ⇒ Object



207
208
209
210
211
212
213
214
215
# File 'lib/cocoapods-generator/command/generator.rb', line 207

def group_for_spec(spec_name, subgroup_key = nil)
if subgroup_key
  group_name = SPEC_SUBGROUPS[subgroup_key]
else
  group_name = spec_name
end

@app_project[group_name] || @app_project.new_group(group_name)
end

#header_mappings_dirObject



358
359
360
361
362
363
# File 'lib/cocoapods-generator/command/generator.rb', line 358

def header_mappings_dir
  file_accessor = @file_accessors.first
  header_mappings_dir = if dir = file_accessor.spec_consumer.header_mappings_dir
                           file_accessor.path_list.root + dir
                         end
end

#installObject



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/cocoapods-generator/command/generator.rb', line 135

def install
  project_path = xcodeproj_path
  @app_project = Xcodeproj::Project.open(project_path)
  @framework_target = @app_project.targets.find { |target| target.name == @spec_content.name }

  create_file_accessors
  add_source_files_references
  add_frameworks_bundles
  add_vendored_libraries
  add_resources

  add_files_to_build_phases
  add_libraries_to_build_phases

  @app_project.save
end

#results_message(results) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/cocoapods-generator/command/generator.rb', line 76

def results_message(results)
  message = ''
  results.each do |result|
    if result.platforms == [:ios]
      platform_message = '[iOS] '
    elsif result.platforms == [:osx]
      platform_message = '[OSX] '
    elsif result.platforms == [:watchos]
      platform_message = '[watchOS] '
    elsif result.platforms == [:tvos]
      platform_message = '[tvOS] '
    end

    subspecs_message = ''
    if result.is_a?(Result)
      subspecs = result.subspecs.uniq
      if subspecs.count > 2
        subspecs_message = '[' + subspecs[0..2].join(', ') + ', and more...] '
      elsif subspecs.count > 0
        subspecs_message = '[' + subspecs.join(',') + '] '
      end
    end

    case result.type
    when :error   then type = 'ERROR'
    when :warning then type = 'WARN'
    when :note    then type = 'NOTE'
    else raise "#{result.type}" end
    message << "    - #{type.ljust(5)} | #{platform_message}#{subspecs_message}#{result.attribute_name}: #{result.message}\n"
  end
  message << "\n"
end

#runObject



62
63
64
65
66
# File 'lib/cocoapods-generator/command/generator.rb', line 62

def run
  create_spec_content
  validatePodspec
  install
end

#validate!Object



53
54
55
56
57
58
59
60
# File 'lib/cocoapods-generator/command/generator.rb', line 53

def validate!
  super

  if @spec_name.nil? || File.extname(@spec_name) != ".podspec"
    help! 'A *.podspec file is required.'
    Process.exit! false
  end
end

#validatePodspecObject



68
69
70
71
72
73
74
# File 'lib/cocoapods-generator/command/generator.rb', line 68

def validatePodspec
  linter = Specification::Linter.new(@spec_path)
  linter.lint
  results = []
  results.concat(linter.results.to_a)
  puts results_message results
end

#xcodeproj_pathObject



113
114
115
116
117
118
119
120
# File 'lib/cocoapods-generator/command/generator.rb', line 113

def xcodeproj_path
  project_path = File.expand_path File.basename(@spec_name, ".podspec") + '.xcodeproj', @current_path
  if !File.exists? project_path
    help! "Please make sure has #{File.basename project_path} in current directory."
    Process.exit! false
  end
  project_path
end