├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cocoapod-to-cordova.rb └── podfile /.gitignore: -------------------------------------------------------------------------------- 1 | Pods/ 2 | build/ 3 | Podfile.lock 4 | build_target_name.txt 5 | product_path.txt 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 mkcode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for building and updating a cordova plugin from a cocoapod spec. 2 | # 3 | # You need to first configure your podfile. 4 | # See the documentation at: 5 | # http://github.com/mkcode/cocoapod-to-cordova.git 6 | # 7 | # Targets: 8 | # 9 | # build: Builds the cocoapod target for release (ios SDKs and archs). 10 | # Use this for vendoring the product into the plugin. 11 | # 12 | # build-sim: Builds the cocoapod target for use in the ios simulator. 13 | # Use this for developing with the ios simulator. 14 | # 15 | 16 | default: build 17 | 18 | pod: clean 19 | pod install --no-integrate 20 | 21 | build: pod 22 | cd Pods && xcodebuild -configuration Release -sdk iphoneos -target $(shell head -n 1 build_target_name.txt) 23 | mv $(shell head -n 1 product_path.txt) $(shell head -n 1 product_path.txt)-ios 24 | cd Pods && xcodebuild -configuration Release -sdk iphonesimulator -target $(shell head -n 1 build_target_name.txt) 25 | mv $(shell head -n 1 product_path.txt) $(shell head -n 1 product_path.txt)-sim 26 | lipo -create $(shell head -n 1 product_path.txt)-ios $(shell head -n 1 product_path.txt)-sim -output $(shell head -n 1 product_path.txt) 27 | rm $(shell head -n 1 product_path.txt)-ios 28 | rm $(shell head -n 1 product_path.txt)-sim 29 | 30 | build-ios: pod 31 | cd Pods && xcodebuild -configuration Release -target $(shell head -n 1 build_target_name.txt) 32 | 33 | build-sim: pod 34 | cd Pods && xcodebuild -configuration Release -sdk iphonesimulator7.1 -target $(shell head -n 1 build_target_name.txt) 35 | 36 | clean: 37 | -rm -rf Pods 38 | -rm Podfile.lock 39 | -rm build_target_name.txt 40 | -rm product_path.txt 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cocoapod-to-cordova 2 | =================== 3 | 4 | Build tools the creates and updates a cordova plugman plugin.xml and vendored product from a cocoapod spec. 5 | * Fetch and build a cocoapod dependency. 6 | * Install and rename the cocoapod source files into the plugin. 7 | * Create and set the required `` plugin.xml elements. 8 | * Correctly handle some common issues in building a cordova plugin 9 | * Freeze the cocoapod dependency version as specified in the podfile. 10 | * Easy to pull in updates from the cocoapod dependency 11 | * Easy to use the best practice of only placing a compiled lib and not source files in the plugin 12 | 13 | This build tool will not: 14 | * Template or write any obj-c wrappers / interfaces 15 | * Template or write any javascript APIs 16 | * Specify modules / clobbering or handle other sections of plugin.xml 17 | 18 | ## Install 19 | * Start creating your plugin following the [Plugin Spec](http://cordova.apache.org/docs/en/3.4.0/plugin_ref_spec.md). Note that you must have the `` element in your plugin.xml before proceeding. 20 | * Clone this repo into your plugin repo. Recommended to put it in 'scripts/update-ios-cocoapod' 21 | ```sh 22 | cd plugin-xml-dir && mkdir -p scripts && cd scripts 23 | git clone https://github.com/mkcode/cocoapod-to-cordova update-ios-cocoapod 24 | ``` 25 | * Configure the `podfile` in `scripts/update-ios-cocoapod`. See below. 26 | * Build 27 | ```sh 28 | cd scripts/update-ios-cocoapod 29 | make # For an ios release 30 | make build-sim # For debugging in the ios simulator 31 | make && make clean # Build and cleanup 32 | ``` 33 | 34 | ## Update 35 | * Update the pod dependency version in `podfile` 36 | ```ruby 37 | pod 'POD_NAME', '-> 3.2' 38 | ``` 39 | 40 | * Rebuild 41 | ```sh 42 | cd scripts/update-ios-cocoapod 43 | make && make clean 44 | ``` 45 | * Update CDVPlugin interfaces if there were any API changes 46 | 47 | ## Configuring the podfile 48 | See http://guides.cocoapods.org/using/the-podfile.html for more info on using podfiles 49 | 50 | Add a platform and a pod entry. See 51 | ```ruby 52 | platform :ios, '6.0' 53 | pod 'POD_NAME', 'POD_VERSION' 54 | ``` 55 | 56 | Integrate `CocoapodToCordovaBuilder` into the post_install hook 57 | ```ruby 58 | post_install do |installer| 59 | require './cocoapod-to-cordova' 60 | build = CocoapodToCordovaBuilder.new('POD_NAME', installer.project) 61 | build.update_xcode_project! 62 | end 63 | ``` 64 | 65 | Set the root_path and destination if they are not the defaults 66 | ```ruby 67 | # The directory path where plugin.xml lives 68 | build.root_path = File.expand_path(File.join('.', '../..')) # default 69 | 70 | # The root installation directory. Relative to the root_path. 71 | build.destination = 'src/ios/vendor' #default 72 | ``` 73 | 74 | CocoapodToCordovaBuilder#configure can take a number of options 75 | ```ruby 76 | build.configure({ 77 | # Product is renamed and moved to the destination dir 78 | product: { name: "libmypod.a" }, 79 | # Include the spanish localization from 'es.lproj'. 80 | localization: 'es', 81 | # Don't copy some headers. Copy the rest to 'destination/head' 82 | headers: { exclude: ['private_header.h', 'other_header.h'], sub_dir: 'head' }, 83 | # Exclude this framework 84 | frameworks: { exclude: ['Foundation.framework'] }, 85 | # Exclude this resource and copy the rest to 'destination/assets' 86 | resources: { sub_dir: 'assets', exclude: ['other-img.png'] } 87 | }) 88 | ``` 89 | ## Cordova Notes 90 | * Cordova plugins will fail to install if there is a filename conflict between the existing cordova project and the incomming plugin. If a generated plugin fails to install when installing with `cordova plugin add ...`, then add the conflicting files to the appropriate `exclude` section of the build configuration. 91 | * Localization files (en.lproj, es.lproj, etc) will always result in file name conflicts. This tool handles them specifically; by excluding them from the resources and only copying the specified .lproj's contained files into the project. See `localization` in `configure` 92 | 93 | ## Examples 94 | This tool was extracted from [Cordova-DBCamera](https://github.com/vulume/Cordova-DBCamera). 95 | See it in action there `scripts/update-ios/cocoapod` 96 | 97 | Podfile in [Cordova-DBCamera](https://github.com/vulume/Cordova-DBCamera) 98 | ```ruby 99 | platform :ios, '6.0' 100 | pod 'DBCamera', git: 'git://github.com/danielebogo/dbcamera.git' 101 | 102 | post_install do |installer| 103 | require './cocoapod-to-cordova' 104 | build = CocoapodToCordovaBuilder.new('DBCamera', installer.project) 105 | build.configure({ 106 | product: { name: 'libdbcamera.a'}, 107 | localization: 'en', 108 | headers: { 109 | exclude: [ 110 | 'DBCameraBaseCropViewController+Private.h', 111 | 'DBCameraBaseCropViewController.h', 112 | 'DBCameraCollectionViewController.h', 113 | 'DBCameraCropView.h', 114 | 'DBCameraGridView.h', 115 | 'DBCameraLibraryViewController.h', 116 | 'DBCameraMacros.h', 117 | 'DBCameraManager.h', 118 | 'DBCameraSegueViewController.h', 119 | 'DBCollectionViewCell.h', 120 | 'DBCollectionViewFlowLayout.h', 121 | 'DBLibraryManager.h', 122 | 'UIImage+Crop.h' 123 | ] 124 | } 125 | }) 126 | build.update_plugin! 127 | end 128 | ``` 129 | 130 | And the generated plugin.xml 131 | ```xml 132 | 133 | 134 | 135 | dbcamera 136 | 137 | 138 | Plugman compatible wrapper for DBCamera. 139 | 140 | 141 | Chris Ewald, Vulume Inc. 142 | 143 | 144 | camera, ios 145 | 146 | 147 | MIT 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | ``` 172 | -------------------------------------------------------------------------------- /cocoapod-to-cordova.rb: -------------------------------------------------------------------------------- 1 | require 'rexml/document' 2 | 3 | # Public CocoapodToCordovaBuilder: Helps to build an Plugman compatible Apache 4 | # Cordova plugin. 5 | # 6 | # Examples 7 | # 8 | # build = CocoapodToCordovaBuilder.new('MyPod', my_xcode_project) 9 | # build.root_path = File.expand_path(File.join('.', '..')) 10 | # build.destination = "src/ios/vendor" 11 | # build.update_plugin! 12 | class CocoapodToCordovaBuilder 13 | attr_accessor :config 14 | attr_reader :target, :plugin_xml 15 | attr_writer :destination, :sourced_attribute, :root_path 16 | 17 | # Public: initialize - setup a new plugin builder. 18 | # 19 | # pod_name - string that is the exact name of the pod specified in the podfile 20 | # xcode_project - See: https://github.com/CocoaPods/Xcodeproj 21 | # The xcode_project object that is passed into the post_install 22 | # hook in a cocoapod podfile. 23 | # 24 | # Examples 25 | # 26 | # In podfile~ 27 | # post_install |installer| do 28 | # build = PluginmanCocoapodBuilder.new('podName', installer.project) 29 | # end 30 | # 31 | # returns an instance of this class ready to be configured 32 | # raises an error if the xcode_project doesn't contain a target for the pod 33 | def initialize(pod_name, xcode_project) 34 | @config = {} 35 | @pod_name = pod_name 36 | @target = xcode_project.targets 37 | .find{|t| t.display_name == "Pods-#{@pod_name}"} 38 | return raise "Could not find a build target in xcodeproj" unless target 39 | end 40 | 41 | # Public: root_path - Set the root_path of the project. This is the same 42 | # directory where the plugin.xml file is located 43 | # 44 | # setter - A string representing the path to the directory with plugin.xml 45 | # 46 | # Examples 47 | # 48 | # build.root_path = "~/Workspace/cordova-plugin" 49 | # 50 | def root_path 51 | @root_path ||= File.expand_path(File.join('.', '../..')) 52 | end 53 | 54 | # Public: desination - The build output directory. Usually 'src/ios/vendor' 55 | # 56 | # setter - A string representing the build output path. The path is 57 | # relative sub directory of the root_path of the project. 58 | # 59 | # Examples 60 | # 61 | # build.destination = 'src/ios/my-pod-source' 62 | # 63 | def destination 64 | @destination ||= 'src/ios/vendor' 65 | end 66 | 67 | # Public: configure - configures the build process. 68 | # 69 | # options - a hash of configuration options 70 | # 71 | # Examples 72 | # 73 | # build.configure({ 74 | # product: { name: 'libmypod.a', sub_dir: 'lib'}, 75 | # localization: 'es', 76 | # headers: { exclude: ['header-1.h', 'header-5.h'] }, 77 | # resources: { sub_dir: 'assets' } 78 | # }) 79 | # 80 | def configure(options) 81 | @config.merge!(options) if options.is_a? Hash 82 | end 83 | 84 | # Public: update_plugin! - runs the build tool. Will overwrite previously 85 | # written elements in plugin.xml and setup extra build phases in 86 | # the xcode_project. Copies files to their destination. This will 87 | # not build / compile the xcodeproject. To do that, run `make build` 88 | # 89 | # Examples 90 | # 91 | # build.update_plugin! 92 | # 93 | def update_plugin! 94 | reset_xml 95 | include_frameworks @config[:frameworks] || {} 96 | include_headers @config[:headers] || {} 97 | 98 | resource_options = @config[:resources] || {} 99 | resource_options[:localization] = @config[:localization] if @config[:localization] 100 | include_resources resource_options 101 | 102 | include_product @config[:product] || {} 103 | write_plugin_xml! @config[:plugin_xml] 104 | write_build_target_file! 105 | end 106 | 107 | private 108 | 109 | def include_product(options={}) 110 | options[:sub_dir] ||= '' 111 | original_product_name = File.basename(@target.product_reference.path) 112 | new_product_name = options[:name] || original_product_name 113 | original_product_path = File.join(dst_path, @target.product_reference.path) 114 | copy_to_product_path = File.join(dst_path, options[:sub_dir], original_product_name) 115 | new_product_path = File.join(dst_path, options[:sub_dir], new_product_name) 116 | new_product_path_relative = File.join(destination, options[:sub_dir], new_product_name) 117 | if copy_files?(options) 118 | copy_phase = create_new_copy_files_build_phase(File.dirname(copy_to_product_path)) 119 | copy_phase.add_file_reference(@target.product_reference) 120 | end 121 | if write_xml?(options) 122 | ios_xml_element.add_element(pod_element 'source-file', 123 | {'framework' => 'true', 124 | 'src' => new_product_path_relative}) 125 | end 126 | if new_product_path != original_product_path 127 | shell_phase = @target.new_shell_script_build_phase 128 | shell_phase.show_env_vars_in_log = "0" 129 | shell_phase.shell_script = "mv #{copy_to_product_path} #{new_product_path}" 130 | end 131 | File.open('product_path.txt', 'w') do |f| 132 | f.puts new_product_path 133 | end 134 | end 135 | 136 | def include_headers(options = {}) 137 | options[:sub_dir] ||= 'headers' 138 | excluded_files = options[:exclude] || [] 139 | if copy_files?(options) 140 | copy_phase = create_new_copy_files_build_phase(File.join(dst_path, options[:sub_dir])) 141 | end 142 | @target.headers_build_phase.files.each do |header| 143 | header_name = File.basename(header.file_ref.path) 144 | unless excluded_files.include?(header_name) 145 | if write_xml?(options) 146 | header_path = File.join(destination, 147 | options[:sub_dir], 148 | header_name) 149 | ios_xml_element.add_element(pod_element('header-file', 150 | {'src' => header_path})) 151 | end 152 | if copy_files?(options) 153 | copy_phase.add_file_reference(header.file_ref) 154 | end 155 | end 156 | end 157 | end 158 | 159 | def include_frameworks(options={}) 160 | excluded_files = options[:exclude] || [] 161 | @target.frameworks_build_phase.files.each do |framework| 162 | unless excluded_files.include?(framework.display_name) 163 | ios_xml_element.add_element(pod_element 'framework', 164 | {'src' => framework.display_name}) 165 | end 166 | end 167 | end 168 | 169 | # Cordova will fail on when adding plugins that contain a filename that allready 170 | # exists inside the project. This always happens with *.lproj files. 171 | def include_resources(options={}) 172 | options[:sub_dir] ||= 'resources' 173 | options[:localization] ||= 'en' 174 | excluded_files = options[:exclude] || [] 175 | resource_group = target.project.pod_group(@pod_name) 176 | .groups 177 | .find{|g| g.display_name == "Resources"} 178 | resource_files = [] 179 | if resource_group 180 | resource_files = resource_group.files 181 | .reject{|f| excluded_files.include?(f.display_name)} 182 | end 183 | if copy_files?(options) 184 | copy_resources = create_new_copy_files_build_phase(File.join(dst_path, options[:sub_dir])) 185 | end 186 | resource_files.each do |resource| 187 | if write_xml?(options) 188 | res_path = File.join(destination, options[:sub_dir], resource.display_name) 189 | if resource.display_name.end_with?('.lproj') 190 | if resource.display_name == "#{options[:localization]}.lproj" 191 | resource.real_path.each_child do |localization_file| 192 | localization_path = File.join(res_path, File.basename(localization_file)) 193 | ios_xml_element.add_element(pod_element('resource-file', 194 | {'src' => localization_path})) 195 | end 196 | end 197 | else 198 | ios_xml_element.add_element(pod_element('resource-file', {'src' => res_path})) 199 | end 200 | end 201 | if copy_files?(options) 202 | copy_resources.add_file_reference(resource) 203 | end 204 | end 205 | end 206 | 207 | def reset_xml 208 | ios_xml_element.each_element_with_attribute(sourced_attribute, "true") do |h| 209 | h.remove 210 | end 211 | end 212 | 213 | def write_plugin_xml!(out_name=nil) 214 | out_name ||= 'plugin.xml' 215 | File.open(File.join(root_path, out_name), 'w') do |file| 216 | plugin_xml.write(file, 2) 217 | end 218 | end 219 | 220 | # This doesn't work from inside the post_install callback. The xcode 221 | # project is not build yet. Instead we write the target and use the shell. 222 | # def exec_xcodebuild! 223 | # exec("cd Pods && xcodebuild -target #{@target.display_name} && cd ..") 224 | # end 225 | 226 | def write_build_target_file! 227 | File.open('build_target_name.txt', 'w') do |f| 228 | f.puts @target.display_name 229 | end 230 | end 231 | 232 | def sourced_attribute 233 | @sourced_attribute ||= 'autogen' 234 | end 235 | 236 | # Setup paths 237 | def dst_path 238 | @dst_path ||= File.join(root_path, destination) 239 | end 240 | 241 | def plugin_xml 242 | @plugin_xml ||= REXML::Document.new(File.new(File.join(root_path, 'plugin.xml'))) 243 | end 244 | 245 | def ios_xml_element 246 | @ios_xml_element ||= REXML::XPath.first(plugin_xml, "//platform[@name='ios']") 247 | end 248 | 249 | def copy_files?(options) 250 | options[:copy_files] != false 251 | end 252 | 253 | def write_xml?(options) 254 | options[:write_xml] != false 255 | end 256 | 257 | def create_new_copy_files_build_phase(copy_to_path) 258 | copy_phase = @target.new_copy_files_build_phase 259 | copy_phase.dst_subfolder_spec = nil 260 | copy_phase.dst_path = copy_to_path 261 | copy_phase 262 | end 263 | 264 | def pod_element(tag_name, attrs={}) 265 | attrs[sourced_attribute] = "true" 266 | element = REXML::Element.new(tag_name) 267 | element.add_attributes(attrs) 268 | element 269 | end 270 | end 271 | -------------------------------------------------------------------------------- /podfile: -------------------------------------------------------------------------------- 1 | platform :ios, 'IOS_VERSION' 2 | pod 'POD_NAME', 'POD_VERSION' 3 | 4 | post_install do |installer| 5 | require './cocoapod-to-cordova' 6 | 7 | # POD_NAME must be the exact same string as specified above 8 | build = CocoapodToCordovaBuilder.new('POD_NAME', installer.project) 9 | 10 | # The directory path where plugin.xml lives 11 | build.root_path = File.expand_path(File.join('.', '../..')) 12 | 13 | # The root installation directory. Relative to the root_path. 14 | build.destination = 'src/ios/vendor' 15 | 16 | # Configuration options. See README examples. 17 | build.configure({ 18 | product: { name: "lib#{POD_NAME}.a"}, 19 | localization: 'en', 20 | headers: { exclude: ['private_header.h'] } 21 | }) 22 | 23 | # Apply the additional build settings to the xcode_project 24 | build.update_plugin! 25 | end 26 | --------------------------------------------------------------------------------