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