├── Gemfile
├── makeDynamic.sh
├── Readme.md
└── makeDynamic.rb
/Gemfile:
--------------------------------------------------------------------------------
1 | source \'https://rubygems.org\'
2 | gem 'cocoapods', '~> 1.2.1'
3 | gem 'xcodeproj', '~> 1.5.0'
4 |
--------------------------------------------------------------------------------
/makeDynamic.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 |
4 | RED="\033[1;31m"
5 | GREEN="\033[0;32m"
6 | TEXT="\033[0m"
7 | RUBY_VERSION="2.2.2"
8 | COCOAPODS_VERSION="1.2.1"
9 | XCODEPROJECT_VERSION="1.5.0"
10 |
11 | # SETP 0. CREATE GEMFILE
12 | echo -e "source \'https://rubygems.org\'\ngem 'cocoapods', '~> ${COCOAPODS_VERSION}'\ngem 'xcodeproj', '~> ${XCODEPROJECT_VERSION}'" > Gemfile
13 |
14 | # STEP 1. INSTALLING RVM
15 | echo -ne "Looking for RVM... "
16 | if ! rvm info &>/dev/null; then
17 | echo -ne "${RED}Not found.${TEXT} Installing... "
18 | \curl -sSL https://get.rvm.io | bash 1> /dev/null
19 | exec bash
20 | echo -e "${GREEN}Done.${TEXT}";
21 | else
22 | echo -e "${GREEN}Found.${TEXT}";
23 | fi
24 |
25 | # STEP 2. INSTALLING RUBY
26 | echo -ne "Looking for RUBY ${RUBY_VERSION}... "
27 | if ! rvm list | grep $RUBY_VERSION &>/dev/null; then
28 | echo -ne "${RED}Not found.${TEXT} Installing... ";
29 | rvm install ${RUBY_VERSION} 1> /dev/null;
30 | echo -e "${GREEN}Done.${TEXT}";
31 | else
32 | echo -e "${GREEN}Found.${TEXT}";
33 | fi
34 |
35 | # STEP 3. INSTALLING BUNDLER
36 | echo -ne "Looking for Bundler Gem... "
37 | if ! gem list -i bundler | grep true &>/dev/null; then
38 | echo -ne "${RED}Not found.${TEXT} Installing... ";
39 | sudo gem install bundler -n /usr/local/bin 1> /dev/null;
40 | echo -e "${GREEN}Done.${TEXT}";
41 | else
42 | echo -e "${GREEN}Found.${TEXT}";
43 | fi
44 |
45 | ruby makeDynamic.rb $@
46 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # Dynamic Library Builder
2 |
3 | Вопросы можно задавать: a.zarembo@tinkoff.ru
4 |
5 | Это пара скриптов, которые помогают делать динамические библиотеки из статических. Они делают многое, но слишком сильно зависят от того, что собирается, поэтому часто приходится залезать внутрь и делать какие-то доработки и хаки.
6 |
7 | Это не универсальное решение, просто помощник, не надо ждать от него слишком многого.
8 |
9 | ## Как использовать
10 |
11 | 1. Нужно апустить скрипт
12 | ```
13 | ./makeDynamic.sh AppsFlyerFramework 4.7.11
14 | ```
15 |
16 | 2. Он должен выполниться без ошибок и дойти до пункта **Running podspec lint quick** и **Done**, на это уйдет около минуты. Если в логе есть ошибки, значит что-то пошло не так и надо ознакомиться с соответствующим разделом этого файла.
17 |
18 | 3. Если все ок, будет создана папка *AppsFlyerFramework*, внутри неё папка *4.7.11* и в ней будет лежать
19 | - **AppsFlyerFrameworkDynamic.framework** - это динамическая библиотека, которая нужна и
20 | - **AppsFlyerFrameworkDynamic.podspec.json** - это Podpec-файл, в который надо прописать путь к git-репозиторию, где будет лежать эта библиотека. Нужно указывать http/https, иначе будет warning и Lint не сработает.
21 |
22 | 2. Надо открыть папку **framework** и убедиться, что там есть бинарник библиотеки, а также что его размер примерно соответствует размеру исходной статической библиотеки.
23 |
24 | Если чего-то нет, надо читать раздел "Если что-то пошло не так".
25 |
26 | 3. Дальше нужно залить библиотеку в репозиторий, проставить тег и проверить `pod spec lint`
27 |
28 | 4. После чего надо залить спеку в репозиторий с помощью `pod repo push ИМЯ_РЕПОЗИТОРИЯ ИМЯСПЕКИ.podspec.json`
29 |
30 | ### Как использовать для сильных духом
31 |
32 | Можно попробовать одной командой библиотеку собрать и залить в репозиторий. Но риск, что что-то пойдет не так очень высок. Команда выглядит так:
33 | ```
34 | ./makeDynamic.sh AppsFlyerFramework 4.7.11 "https://stash.tcsbank.ru/scm/mip/appsflyerframeworkdynamic.git"
35 | ```
36 |
37 | Если все пройдет хорошо, скрип сам проставит путь в pod spec, все зальет и сделает tag.
38 |
39 | ## Если что-то пошло не так
40 |
41 | ### Как это все работает
42 |
43 | 1. Сначала bash-скрипт **makeDynamic.sh** выполняет настройку среды Ruby. Создает Gemfile, ставит RVM, Ruby и Bundler, после чего ставит cocoa pods.
44 |
45 |
46 | Делает он это от имени root, поэтому спросит системный пароль.
47 |
48 | 2. Когда среда настроена, запускается скрипт **makeDynamic.rb**
49 | 3. Этот скрипт создает папки по имени библиотеки и её версии. Если папка уже была, она сначала удаляется.
50 | 4. В папке с версией создает Xcode-проект
51 | 5. В проекте создает таргет для динамической библиотеки. Имя библиотеки такое же + Dynamic
52 | 6. Генерирует Info.plist
53 | 7. Создает .h-файл для динамической библиотеки
54 | 8. Создает Podfile и добавляет туда имя библиотеки и версию, которые передали в параметрах
55 | 9. Запускает `pod install`
56 | 10. Открывает созданный Workspace, в нем Pods-проект и ищет заголовочные файлы статической библиотеки
57 | 11. Все найденные .h-файлы добавляет в динамическую библиотеку как public
58 | 12. Потом все найденные .h-файлы добавляет в .h-файл динамической библиотеки
59 | 13. Снова запускает `pod Install`. Не знаю, в чем дело, но первый раз он не проставляет связи на статическую библиотеку
60 | 14. Создает Shared-схему для сборки проекта
61 | 15. Дальше запускается скрипт **buildLib.rb**
62 | 16. Этот скрипт собирает библиотеку для симулятора
63 | 17. После чего собирает библиотеку для устройств
64 | 18. Склеивает их через lipo
65 | 19. Копирует framework устройства в папку, созданную в п.3 и заменяет бинарник на объединенный
66 | 20. Дальше удаляются все вспомогательные файлы и папки, остается только .framework
67 | 21. Из списка репозиториев выгружается оригинальный podspec библиотеки
68 | 22. В него прописывается новый `vendored_framework`, `preserve_paths`, обновляется версия iOS до 8, меняется имя и удаляется поле Source
69 | 23. И он сохраняется в папку
70 | 24. Если был указан путь к git-репозиторию, он прописывается в pod spec поле source и заливается в git
71 |
72 |
73 | ### Какие бывают проблемы
74 |
75 | #### Ошибка с Ruby на этапе 1.
76 |
77 | Надо гуглить, увы.
78 |
79 | #### Ошибка при работе скрипта где-то 146-150 строки
80 |
81 | Это значит, что в проекте используется какая-то хитрая схема расположения библиотек. Например такая есть в GoogleMaps. Тут нужно руками поправить пути в скрипте, сделать что-то вроде
82 | ```
83 | frameworks_group = library_group['Maps']['Frameworks']
84 | ```
85 |
86 | И перезапустить скрипт.
87 |
88 | #### .framework есть, а внутри пусто
89 |
90 | Скорее всего не прошла сборка. Поэтому нужно закомментировать в скрипте блок Cleanup в районе 195-205 строк и перезапустить скрипт. После сборки в папке будет два файла:
91 | ```
92 | buildlog_simulator.txt
93 | buildlog_device.txt
94 | ```
95 | Нужно их открыть и посмотреть что пошло не так, поправить. После чего можно запустить скрипт buildLib.sh с теми же параметрами, что были в makeDynamic.sh и должен собраться .framework c библиотекой. Podpec придется сделать руками
96 |
97 | #### Ошибки при загрузке сразу в репозиторий
98 |
99 | Надо смотреть в консоле. У меня было так, что swap-файл поломался, помогло переписать.
100 |
101 | ### Что было бы круто улучшить
102 |
103 | 1. Сделать нормальную работу с ключами командной строки
104 | 2. Добавить ключ **--no-cleanup**, чтобы скрипт не править
105 | 3. Добавить ключ **--rebuild-only**, чтобы проект не пересозавался, а только пересобирался и перепаковывался
106 | 4. Понять, почему приходится два раза делать `pod install` и обойтись одним
107 | 5. Добавить ключ **--source-git**, чтобы в него указывать путь к репозитория
108 | 6. Добавить ключ **--pod-repo**, чтобы сразу делать Lint и заливать pod spec
109 | 7. Добавить ключ **--group-path**, чтобы такие вещи, как GoogleMaps можно было собирать без правки скрипта
110 | 8. Добавить сборку в отдельную папку, а также **gitignore** на неё, чтобы скрипты не попадали в git Status скриптов
111 |
--------------------------------------------------------------------------------
/makeDynamic.rb:
--------------------------------------------------------------------------------
1 | require 'securerandom'
2 | require 'fileutils'
3 | require 'open-uri'
4 | require 'xcodeproj'
5 | require 'json'
6 |
7 | puts ""
8 |
9 | libraryName = ARGV[0]
10 | libraryVersion = ARGV[1]
11 | git_url = ARGV[2]
12 |
13 | if !libraryName || !libraryVersion
14 | puts "./makeDynamic.sh PodName PodVersion [git upload url]"
15 | exit(1)
16 | end
17 |
18 | puts "Making dynamic librarty for "+libraryName+" ["+libraryVersion+"]"
19 |
20 | # Directory
21 |
22 | libraryRootDir = URI::encode(libraryName)
23 | libraryVersionDir = URI::encode(libraryVersion)
24 | libraryPath = libraryRootDir+"/"+libraryVersionDir
25 |
26 | if File.exists?(libraryPath)
27 | puts "Recreating folder "+libraryPath+" for library"
28 | FileUtils.rm_r libraryPath
29 | FileUtils.mkdir_p libraryPath
30 | else
31 | puts "Creating folder "+libraryPath+" for library"
32 | FileUtils.mkdir_p libraryPath unless File.exists?(libraryPath)
33 | end
34 |
35 | # Xcode project
36 |
37 | projectName = (libraryName+libraryVersion).tr('.','_').tr(' ','').tr('-','_').gsub(/[^0-9a-z_]/i, '')
38 | project_path = libraryPath+"/"+projectName+".xcodeproj"
39 |
40 | # Creating project
41 |
42 | puts "Creating xcode project "+projectName+" for library"
43 | project = Xcodeproj::Project.new(project_path)
44 | project.save
45 |
46 | # Creating target
47 |
48 | puts "Creating framework target in project"
49 | frameworkName = libraryName+"Dynamic"
50 | framework_target = project.new_target(:framework, frameworkName, :ios, '8.0')
51 | framework_target.add_system_framework("UIKit")
52 | project.save
53 |
54 | # Creating Info.plist
55 |
56 | puts "Creating Info.plist"
57 |
58 | info_plist_path = libraryPath+"/Info.plist"
59 | File.write(info_plist_path, '''
60 |
61 |
62 |
63 |
64 | CFBundleDevelopmentRegion
65 | en
66 | CFBundleExecutable
67 | $(EXECUTABLE_NAME)
68 | CFBundleIdentifier
69 | com.dynamic.'+frameworkName+'
70 | CFBundleInfoDictionaryVersion
71 | 6.0
72 | CFBundleName
73 | $(PRODUCT_NAME)
74 | CFBundlePackageType
75 | FMWK
76 | CFBundleShortVersionString
77 | 1.0
78 | CFBundleVersion
79 | $(CURRENT_PROJECT_VERSION)
80 | NSPrincipalClass
81 |
82 |
83 |
84 | ''')
85 |
86 | info_plist = project.new_file("./Info.plist")
87 | framework_target.add_file_references([info_plist])
88 | for build_configuration in framework_target.build_configurations
89 | build_configuration.build_settings['INFOPLIST_FILE']='Info.plist'
90 | build_configuration.build_settings['SWIFT_VERSION']='3.0'
91 | end
92 | project.save
93 |
94 |
95 | # Creating Framework header
96 |
97 | puts "Creating framework header"
98 | header_file_name = frameworkName+".h"
99 | header_file_path = libraryPath+"/"+header_file_name
100 | File.write(header_file_path, '
101 | #import
102 |
103 | FOUNDATION_EXPORT double '+frameworkName+'VersionNumber;
104 | FOUNDATION_EXPORT const unsigned char '+frameworkName+'VersionString[];
105 | ')
106 |
107 | header_file = project.new_file(header_file_name)
108 | framework_target.add_file_references([header_file])
109 | framework_target.headers_build_phase.files[-1].settings = { "ATTRIBUTES" => ["Public"] }
110 | project.save
111 |
112 | # Creating podfile
113 |
114 | puts "Creating Podfile"
115 | podfile_name = "Podfile"
116 | podfile_path = libraryPath+"/"+podfile_name
117 | File.write(podfile_path, '''
118 | platform :ios, \'8.0\'
119 | inhibit_all_warnings!
120 |
121 | use_frameworks!
122 |
123 | target \''+frameworkName+'\' do
124 | pod \''+libraryName+'\', \''+libraryVersion+'\'
125 | end
126 | ''')
127 |
128 | # Running pod install
129 | puts "Running 'pod install'"
130 | command = 'pod install '+'--project-directory="'+'./'+libraryPath+'"'
131 | result = `#{command}`
132 | puts "--Done--"
133 |
134 | puts "Reading headers"
135 | # Add library headers to current project
136 | workspace_name = projectName+".xcworkspace"
137 | workspace_path = libraryPath+"/"+workspace_name
138 | workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path)
139 |
140 | for project_file in workspace.file_references
141 | if File.basename(project_file.path,".xcodeproj") == "Pods"
142 | puts "Found Pods project: "+libraryPath+"/"+project_file.path
143 | pods_project = Xcodeproj::Project.open(libraryPath+"/"+project_file.path)
144 | end
145 | end
146 |
147 | puts "Looking for framework files"
148 | library_group = pods_project['Pods'][libraryName]
149 | frameworks_group = library_group['Frameworks']
150 | framework_group = frameworks_group.children[0]
151 | framework_header_folder = framework_group.real_path+"Headers"
152 |
153 | puts "Adding headers to library project"
154 | for library_header_file in framework_header_folder.children
155 | header_file = project.new_file(library_header_file)
156 | framework_target.add_file_references([header_file])
157 | framework_target.headers_build_phase.files[-1].settings = { "ATTRIBUTES" => ["Public"] }
158 | project.save
159 | end
160 |
161 | puts "Updating library header file"
162 | header_file_string = "#import \n"
163 |
164 | for library_header_file in framework_header_folder.children
165 | header_file_string += "#import \""+library_header_file.basename.to_s+"\"\n"
166 | end
167 |
168 | header_file_string += "FOUNDATION_EXPORT double "+frameworkName+"VersionNumber;\n"
169 | header_file_string += "FOUNDATION_EXPORT const unsigned char "+frameworkName+"VersionString[];\n"
170 |
171 | File.write(header_file_path, header_file_string)
172 |
173 | # Running pod install
174 | puts "Running 'pod install' second time"
175 | command = 'pod install '+'--project-directory="'+'./'+libraryPath+'"'
176 | result = `#{command}`
177 | puts "--Done--"
178 |
179 | # Update pods path
180 |
181 | project = Xcodeproj::Project.open(project_path)
182 | frameworkName = libraryName+"Dynamic"
183 |
184 | # Make scheme shared
185 | puts "Making scheme shared"
186 | project.recreate_user_schemes(true)
187 | Xcodeproj::XCScheme.share_scheme(project_path, frameworkName)
188 |
189 | # Build libraryPath
190 | libary_build_command = 'ruby buildLib.rb '+libraryName+' '+libraryVersion
191 | library_build_result = `#{libary_build_command}`
192 | puts library_build_result
193 |
194 | puts "Cleanup"
195 | FileUtils.rm_r './'+libraryPath+'/buildlog_device.txt'
196 | FileUtils.rm_r './'+libraryPath+'/buildlog_simulator.txt'
197 | FileUtils.rm_r './'+libraryPath+'/derived_data'
198 | FileUtils.rm_r './'+libraryPath+'/Podfile'
199 | FileUtils.rm_r './'+libraryPath+'/Pods'
200 | FileUtils.rm_r './'+libraryPath+'/Podfile.lock'
201 | FileUtils.rm_r './'+libraryPath+'/'+projectName+".xcodeproj"
202 | FileUtils.rm_r './'+libraryPath+'/'+projectName+".xcworkspace"
203 | FileUtils.rm_r './'+libraryPath+'/Info.plist'
204 | FileUtils.rm_r './'+libraryPath+'/'+header_file_name
205 |
206 | puts "Loading podspec"
207 | podspecs_command = 'pod spec which '+libraryName+' --show-all'
208 | all_podspecs = `#{podspecs_command}`
209 |
210 | for podspec_path in all_podspecs.split("\n")
211 | if podspec_path.include? libraryName+"/"+libraryVersion
212 | podspec_file = file = File.read(podspec_path)
213 | end
214 | end
215 |
216 | puts "Reading to JSON"
217 | data_hash = JSON.parse(podspec_file)
218 | if git_url
219 | data_hash["source"] = {"git": git_url,"tag": libraryVersion}
220 | end
221 | data_hash["vendored_frameworks"] = [frameworkName+'.framework']
222 | data_hash["preserve_paths"] = [frameworkName+'.framework']
223 | data_hash["source_files"] = nil
224 | data_hash["name"] = frameworkName
225 | data_hash["platforms"] = {'ios':'8.0'}
226 |
227 | File.open("./"+libraryPath+"/"+frameworkName+".podspec.json","w") do |f|
228 | f.write(JSON.pretty_generate(data_hash))
229 | end
230 |
231 | puts "Running podspec lint quick"
232 | puts
233 | pod_lint_quick_command = 'pod spec lint --quick ./'+libraryPath+"/"+frameworkName+".podspec.json"
234 | lint_result = `#{pod_lint_quick_command}`
235 | puts lint_result
236 |
237 | if git_url
238 | puts "Uploading to git"
239 | git_init_command = 'git init; \
240 | git remote add origin '+git_url+'; \
241 | git add -A; \
242 | git commit -m "Uploading library '+frameworkName+' with version '+libraryVersion+'"; \
243 | git tag -a '+libraryVersion+' -m ""; \
244 | git push -u origin master; \
245 | git push origin --tags'
246 | puts git_init_command
247 | Dir.chdir('./'+libraryPath){
248 | exec(git_init_command)
249 | }
250 |
251 | end
252 |
253 | puts
254 | puts 'Done'
255 | puts
256 |
--------------------------------------------------------------------------------