Help us understand the problem. What is going on with this article?

xcodeprojでxcodeをいじる

xcodeprojとは?

xcodeprojはRubyのgemであり、xcodeないの設定などをrubyでいじるために使われる物である。
例をあげるとschemeなどの作成やターゲットの作成、buildsettingやgroupの作成などxcodeに関係する様々な設定を操作可能にします。

使用例

※以下コードはアプリケーションのマスク替えを効率化する事を目的に記述されています。

require 'rubygems'

require 'xcodeproj'

name = 'test_copy'

proj = Xcodeproj::Project.open('test.xcodeproj')
src_target = proj.targets.find { |item| item.to_s == 'test' }

# create target
target = proj.new_target(src_target.symbol_type, name, src_target.platform_name, src_target.deployment_target)

# create scheme
scheme = Xcodeproj::XCScheme.new
scheme.add_build_target(target)
scheme.set_launch_target(target)
scheme.save_as(proj_path, name)

# copy build_configurations
target.build_configurations.map do |item|
item.build_settings.update(src_target.build_settings(item.name))
end

# copy build_phases
phases = src_target.build_phases.reject { |x| x.instance_of? Xcodeproj::Project::Object::PBXShellScriptBuildPhase }.collect(&:class)

phases.each do |klass|
   src = src_target.build_phases.find { |x| x.instance_of? klass }
   dst = target.build_phases.find { |x| x.instance_of? klass }
   unless dst
     dst ||= proj.new(klass)
     target.build_phases << dst
   end
   dst.files.map { |x| x.remove_from_project } 

   src.files.each do |f| 
     file_ref = proj.new(Xcodeproj::Project::Object::PBXFileReference)
     file_ref.name = f.file_ref.name
     file_ref.path = f.file_ref.path
     file_ref.source_tree = f.file_ref.source_tree
     file_ref.last_known_file_type = f.file_ref.last_known_file_type
     file_ref.fileEncoding = f.file_ref.fileEncoding
     begin
       file_ref.move(f.file_ref.parent) 
     rescue
   end

  build_file = proj.new(Xcodeproj::Project::Object::PBXBuildFile)

     build_file.file_ref = f.file_ref
     dst.files << build_file
   end
 end

 # add files
 classes = proj.main_group.groups.find { |x| x.to_s == 'Group' }.groups.find { |x| x.name == 'Classes' } 
 sources = target.build_phases.find { |x| x.instance_of? Xcodeproj::Project::Object::PBXSourcesBuildPhase }
 file_ref = classes.new_file('test.m')
 build_file = proj.new(Xcodeproj::Project::Object::PBXBuildFile)
 build_file.file_ref = file_ref
 sources.files << build_file

 proj.save

それではまず下記のコードの解説をしていきます。
まずインストールしたgemを使用するために、requireで必要gemの使用を可能にします。
そしてnameにアプリケーション名を代入し、projに各当プロジェクトの名称を代入します。
次にsrc_targetにコピー元のターゲットを代入します。

require 'rubygems'

require 'xcodeproj'

name = 'test_copy'

proj = Xcodeproj::Project.open('test.xcodeproj')
src_target = proj.targets.find { |item| item.to_s == 'test' }

次にターゲットの作成を行います。
まず先ほど定義したsrc_target(コピー元ファイル)のsymbol_type(例 :application,:frameworkなど)を設定し、名前の設定し、platform_nameは:iosか:osxを設定します。devekopment_targetは調べてもよくわからなかったので、nilを指定してあげるとターゲットがbuildされました。
説明が不十分な点ご了承ください。
下記にこのメソッドの公式リファレンスを貼っておきます。
https://www.rubydoc.info/github/CocoaPods/Xcodeproj/Xcodeproj%2FProject%2FProjectHelper.new_target

# create target
target = proj.new_target(src_target.symbol_type, name, src_target.platform_name, src_target.deployment_target)

次にスチーマのbuildを行います。
設置は下記の通りで、スチーマにadd_build_targetで各当のターゲットを設定します。
set_launch_targetに関しては必要性がわからなかったのですが、なくても問題なくターゲットも設定されたスチーマがbuildされていました。気になる方は検証してみてください。
最後にsave_asメソッドでプロジェクトのパスとスチーマ名を設定し保存で完了です。

# create scheme
scheme = Xcodeproj::XCScheme.new
scheme.add_build_target(target)
scheme.set_launch_target(target)
scheme.save_as(proj_path, name)

次にbuild_phasesのコピーを行います。
まずphasesにsrc_targetのbuild_phases内のrun_script(Xcodeproj::Project::Object::PBXShellScriptBuildPhase)をrejectメソッドで取り除き必要な項目だけを残して代入します。
次にsrcにコピー元のbuild_phasesを見つけ代入します。そしてdstに先ほどbuildしたターゲットのbuild_phasesを見つけ代入します。そしてunless文でdstにない項目を特定し、ないものを加えます。
次に加えられた項目をremove_from_projectで前のプロジェクト情報を取り除きます。
そして次に、各ファイルに新しいプロジェクト情報や名前、パスなどをsrc(元ターゲット)のファイルに設定していきます。
そしてbuild_fileに新しく空のfile_refを作り、そこに先ほど再設定した元ファイルのfile_refを代入します。
最後にこれを新ターゲットのファイルに加えることで、ターゲット情報が変わった新ファイルを生成することができます。

# copy build_phases
phases = src_target.build_phases.reject { |x| x.instance_of? Xcodeproj::Project::Object::PBXShellScriptBuildPhase }.collect(&:class)

phases.each do |klass|
   src = src_target.build_phases.find { |x| x.instance_of? klass }
   dst = target.build_phases.find { |x| x.instance_of? klass }
   unless dst
     dst ||= proj.new(klass)
     target.build_phases << dst
   end
   dst.files.map { |x| x.remove_from_project } 

   src.files.each do |f| 
     file_ref = proj.new(Xcodeproj::Project::Object::PBXFileReference)
     file_ref.name = f.file_ref.name
     file_ref.path = f.file_ref.path
     file_ref.source_tree = f.file_ref.source_tree
     file_ref.last_known_file_type = f.file_ref.last_known_file_type
     file_ref.fileEncoding = f.file_ref.fileEncoding
     begin
       file_ref.move(f.file_ref.parent) 
     rescue
   end

  build_file = proj.new(Xcodeproj::Project::Object::PBXBuildFile)

     build_file.file_ref = f.file_ref
     dst.files << build_file
   end
 end

次は新しいファイルを加えるための処理を行なっていきます。
まず最初にclassesにファイルを作成するグループを代入します。
次に各当ターゲットのbuild_phasesをsourcesに代入します。
そしてfile_refにclassesに新しくファイルを生成し代入し、build.fileにfile_refに代入し、sourcesのファイルの方にも加えて完了です。

# add files
 classes = proj.main_group.groups.find { |x| x.to_s == 'Group' }.groups.find { |x| x.name == 'Classes' } 
 sources = target.build_phases.find { |x| x.instance_of? Xcodeproj::Project::Object::PBXSourcesBuildPhase }
 file_ref = classes.new_file('test.m')
 build_file = proj.new(Xcodeproj::Project::Object::PBXBuildFile)
 build_file.file_ref = file_ref
 sources.files << build_file

最後に変更点を下記のコードで保存して終了になります。

proj.save

最後に

私自身まだまだ理解が足りていないので、説明が曖昧な箇所多々あると思いますがご了承ください。
さらに理解を深めたい方は下記に参考リンクを載せておくので、ご利用ください。

参考文献

Github
https://github.com/CocoaPods/Xcodeproj

公式リファレンス
https://www.rubydoc.info/gems/xcodeproj/Xcodeproj/Project

参考記事
https://gist.github.com/ratazzi/f6d9217654d6605450a0

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away