├── .github └── workflows │ └── cla.yml ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── cocoapods_art.gemspec └── lib ├── art_source.rb ├── cocoapods_art.rb ├── cocoapods_plugin.rb ├── pod ├── artifactory_repo.rb └── command │ ├── repo_art.rb │ └── repo_art │ ├── add.rb │ ├── lint.rb │ ├── list.rb │ ├── push.rb │ ├── remove.rb │ └── update.rb └── util └── repo_util.rb /.github/workflows/cla.yml: -------------------------------------------------------------------------------- 1 | name: "CLA Assistant" 2 | on: 3 | # issue_comment triggers this action on each comment on issues and pull requests 4 | issue_comment: 5 | types: [created] 6 | pull_request_target: 7 | types: [opened,synchronize] 8 | 9 | jobs: 10 | CLAssistant: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions-ecosystem/action-regex-match@v2 14 | id: sign-or-recheck 15 | with: 16 | text: ${{ github.event.comment.body }} 17 | regex: '\s*(I have read the CLA Document and I hereby sign the CLA)|(recheckcla)\s*' 18 | 19 | - name: "CLA Assistant" 20 | if: ${{ steps.sign-or-recheck.outputs.match != '' || github.event_name == 'pull_request_target' }} 21 | # Alpha Release 22 | uses: cla-assistant/github-action@v2.1.1-beta 23 | env: 24 | # Generated and maintained by github 25 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | # JFrog organization secret 27 | PERSONAL_ACCESS_TOKEN : ${{ secrets.CLA_SIGN_TOKEN }} 28 | with: 29 | path-to-signatures: 'signed_clas.json' 30 | path-to-document: 'https://jfrog.com/cla/' 31 | remote-organization-name: 'jfrog' 32 | remote-repository-name: 'jfrog-signed-clas' 33 | # branch should not be protected 34 | branch: 'master' 35 | allowlist: bot* 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | /.config 4 | /coverage/ 5 | /InstalledFiles 6 | /pkg/ 7 | /spec/reports/ 8 | /spec/examples.txt 9 | /test/tmp/ 10 | /test/version_tmp/ 11 | /tmp/ 12 | 13 | ## Specific to RubyMotion: 14 | .dat* 15 | .repl_history 16 | build/ 17 | 18 | ## Documentation cache and generated files: 19 | /.yardoc/ 20 | /_yardoc/ 21 | /doc/ 22 | /rdoc/ 23 | 24 | ## Environment normalization: 25 | /.bundle/ 26 | /vendor/bundle 27 | /lib/bundler/man/ 28 | 29 | # for a library or gem, you might want to ignore these files since the code is 30 | # intended to run in multiple environments; otherwise, check them in: 31 | # Gemfile.lock 32 | # .ruby-version 33 | # .ruby-gemset 34 | 35 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 36 | .rvmrc 37 | 38 | # idea 39 | .idea/ 40 | *.iml 41 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem 'cocoapods' 7 | gem 'cocoapods-core' 8 | gem 'cocoapods-downloader' 9 | gem 'claide' 10 | 11 | # test 12 | # gem 'bacon' 13 | # gem 'mocha-on-bacon' 14 | # gem 'prettybacon' 15 | 16 | # build 17 | # gem 'rubocop' 18 | end 19 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | cocoapods-art (1.1.1) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | CFPropertyList (3.0.6) 10 | rexml 11 | activesupport (7.0.7.2) 12 | concurrent-ruby (~> 1.0, >= 1.0.2) 13 | i18n (>= 1.6, < 2) 14 | minitest (>= 5.1) 15 | tzinfo (~> 2.0) 16 | addressable (2.8.5) 17 | public_suffix (>= 2.0.2, < 6.0) 18 | algoliasearch (1.27.5) 19 | httpclient (~> 2.8, >= 2.8.3) 20 | json (>= 1.5.1) 21 | atomos (0.1.3) 22 | claide (1.1.0) 23 | cocoapods (1.12.1) 24 | addressable (~> 2.8) 25 | claide (>= 1.0.2, < 2.0) 26 | cocoapods-core (= 1.12.1) 27 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 28 | cocoapods-downloader (>= 1.6.0, < 2.0) 29 | cocoapods-plugins (>= 1.0.0, < 2.0) 30 | cocoapods-search (>= 1.0.0, < 2.0) 31 | cocoapods-trunk (>= 1.6.0, < 2.0) 32 | cocoapods-try (>= 1.1.0, < 2.0) 33 | colored2 (~> 3.1) 34 | escape (~> 0.0.4) 35 | fourflusher (>= 2.3.0, < 3.0) 36 | gh_inspector (~> 1.0) 37 | molinillo (~> 0.8.0) 38 | nap (~> 1.0) 39 | ruby-macho (>= 2.3.0, < 3.0) 40 | xcodeproj (>= 1.21.0, < 2.0) 41 | cocoapods-core (1.12.1) 42 | activesupport (>= 5.0, < 8) 43 | addressable (~> 2.8) 44 | algoliasearch (~> 1.0) 45 | concurrent-ruby (~> 1.1) 46 | fuzzy_match (~> 2.0.4) 47 | nap (~> 1.0) 48 | netrc (~> 0.11) 49 | public_suffix (~> 4.0) 50 | typhoeus (~> 1.0) 51 | cocoapods-deintegrate (1.0.5) 52 | cocoapods-downloader (1.6.3) 53 | cocoapods-plugins (1.0.0) 54 | nap 55 | cocoapods-search (1.0.1) 56 | cocoapods-trunk (1.6.0) 57 | nap (>= 0.8, < 2.0) 58 | netrc (~> 0.11) 59 | cocoapods-try (1.2.0) 60 | colored2 (3.1.2) 61 | concurrent-ruby (1.2.2) 62 | escape (0.0.4) 63 | ethon (0.16.0) 64 | ffi (>= 1.15.0) 65 | ffi (1.15.5) 66 | fourflusher (2.3.1) 67 | fuzzy_match (2.0.4) 68 | gh_inspector (1.1.3) 69 | httpclient (2.8.3) 70 | i18n (1.14.1) 71 | concurrent-ruby (~> 1.0) 72 | json (2.6.3) 73 | minitest (5.19.0) 74 | molinillo (0.8.0) 75 | nanaimo (0.3.0) 76 | nap (1.1.0) 77 | netrc (0.11.0) 78 | public_suffix (4.0.7) 79 | rake (0.9.6) 80 | rexml (3.2.6) 81 | ruby-macho (2.5.1) 82 | typhoeus (1.4.0) 83 | ethon (>= 0.9.0) 84 | tzinfo (2.0.6) 85 | concurrent-ruby (~> 1.0) 86 | xcodeproj (1.22.0) 87 | CFPropertyList (>= 2.3.3, < 4.0) 88 | atomos (~> 0.1.3) 89 | claide (>= 1.0.2, < 2.0) 90 | colored2 (~> 3.1) 91 | nanaimo (~> 0.3.0) 92 | rexml (~> 3.2.4) 93 | 94 | PLATFORMS 95 | ruby 96 | 97 | DEPENDENCIES 98 | bundler (~> 2.4.19) 99 | claide 100 | cocoapods 101 | cocoapods-art! 102 | cocoapods-core 103 | cocoapods-downloader 104 | rake (~> 0) 105 | 106 | BUNDLED WITH 107 | 2.4.19 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Gem Version](https://badge.fury.io/rb/cocoapods-art.svg)](https://badge.fury.io/rb/cocoapods-art) 2 | 3 | # cocoapods-art 4 | A CocoaPods Plugin to work with Artifactory Repository 5 | 6 | ## Installation 7 | `gem install cocoapods-art` 8 | 9 | ## Client Configuration 10 | To add an Artifactory repository named 'myRepo' to your client: 11 | ``` 12 | pod repo-art add artifactory-local http://art-prod.company.com:8081/artifactory/api/pods/myRepo 13 | ``` 14 | 15 | To use 'myRepo' to resolve pods when installing you must add the following to your Podfile: 16 | ```ruby 17 | plugin 'cocoapods-art', :sources => [ 18 | '' 19 | ] 20 | ``` 21 | More than one source can be included, separated by commas. 22 | 23 | For authenticated access, please add the user and password to your .netrc file: 24 | ``` 25 | machine art-prod.company.com 26 | login admin 27 | password password 28 | ``` 29 | You can also use an encrypted Artifactory password or your API key 30 | 31 | If your .netrc file is not located in your home directory, you can specify its location in the environmental variable: 32 | ``` 33 | export COCOAPODS_ART_NETRC_PATH=$HOME/myproject/.netrc 34 | ``` 35 | 36 | Alternatively, you can specify a username and password/API key directly from an environment variable rather than utilizing the .netrc file, by setting the value of `COCOAPODS_ART_CREDENTIALS` to your Artifactory username and the password separated by a colon: 37 | 38 | ``` 39 | export COCOAPODS_ART_CREDENTIALS="admin:password" 40 | ```` 41 | 42 | If the `COCOAPODS_ART_CREDENTIALS` variable is set, its value will supersede any credentials specified in your .netrc file, causing them to be ignored. 43 | 44 | You could set the following environment variable: COCOAPODS_ART_SSL_NO_REVOKE, this will add the flag --ssl-no-revoke to curl command. 45 | If your are running on an environment where access to CRL isn't available you would still be able to access Artifactory via HTTPS using the cocoapods-art plugin on windows. 46 | ``` 47 | set COCOAPODS_ART_SSL_NO_REVOKE=true 48 | ``` 49 | 50 | ## Artifactory Configuration 51 | See the [Artifactory User Guide](https://www.jfrog.com/confluence/display/RTF/CocoaPods+Repositories) 52 | 53 | ## The cocoapods-art plugin exposes all `pod repo` commands by using `pod repo-art`: 54 | ``` 55 | pod repo-art add 56 | pod repo-art lint 57 | pod repo-art list 58 | pod repo-art remove 59 | pod repo-art update 60 | ``` 61 | ## Special notes 62 | Contrary to the default behavior, the cocoapods-art plugin does not implicitly update your sources when actions such as `add` run. 63 | To update a repo use `pod repo-art update` 64 | 65 | `pod repo-art update` is an accumulative operation, meaning that it does not remove entries which do not exist in the Artifactory backend in order to preserve entries that were created with the `--local-only` flag. To have all such entries removed use the update command with the `--prune` flag. 66 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | 2 | #-----------------------------------------------------------------------# 3 | # Bootstrap 4 | #-----------------------------------------------------------------------# 5 | 6 | desc 'Initializes your working copy to run the specs' 7 | task :bootstrap do 8 | if system('which bundle') 9 | title 'Installing gems' 10 | sh 'bundle install' 11 | else 12 | $stderr.puts "\033[0;31m" \ 13 | "[!] Please install the bundler gem manually:\n" \ 14 | ' $ [sudo] gem install bundler' 15 | "\e[0m" 16 | exit 1 17 | end 18 | end 19 | 20 | begin 21 | require 'bundler/gem_tasks' 22 | task :default => :spec 23 | 24 | #-----------------------------------------------------------------------# 25 | # Specs 26 | #-----------------------------------------------------------------------# 27 | 28 | desc 'Runs all the specs' 29 | task :spec do 30 | title 'Running Unit Tests' 31 | files = FileList['spec/**/*_spec.rb'].shuffle.join(' ') 32 | sh "bundle exec bacon #{files}" 33 | 34 | #title 'Checking code style...' 35 | #Rake::Task['rubocop'].invoke if RUBY_VERSION >= '1.9.3' 36 | end 37 | 38 | #-----------------------------------------------------------------------# 39 | # Rubocop 40 | #-----------------------------------------------------------------------# 41 | 42 | if RUBY_VERSION >= '1.9.3' 43 | require 'rubocop/rake_task' 44 | RuboCop::RakeTask.new 45 | end 46 | 47 | end 48 | 49 | #-----------------------------------------------------------------------# 50 | # Helpers 51 | #-----------------------------------------------------------------------# 52 | 53 | def title(title) 54 | cyan_title = "\033[0;36m#{title}\033[0m" 55 | puts 56 | puts '-' * 80 57 | puts cyan_title 58 | puts '-' * 80 59 | puts 60 | end 61 | -------------------------------------------------------------------------------- /cocoapods_art.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'cocoapods_art' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'cocoapods-art' 8 | spec.version = CocoaPodsArt::VERSION 9 | spec.authors = ['Dan Feldman'] 10 | spec.email = ['art-dev@jfrog.com'] 11 | spec.description = %q{Enables you to use Artifactory as your spec repo, as well as a repository for your pods} 12 | spec.summary = %q{Artifactory support for CocoaPods} 13 | spec.homepage = 'https://github.com/JFrogDev/cocoapods-art' 14 | spec.license = 'Apache-2.0' 15 | 16 | spec.files = Dir['lib/**/*.rb'] 17 | spec.files += Dir['[A-Z]*'] + Dir['test/**/*'] 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ['lib'] 20 | 21 | spec.add_development_dependency 'bundler', '~> 2.4.19' 22 | spec.add_development_dependency 'rake', '~> 0' 23 | end 24 | -------------------------------------------------------------------------------- /lib/art_source.rb: -------------------------------------------------------------------------------- 1 | module Pod 2 | # Subclass of Pod::Source to provide support for Artifactory Specs repositories 3 | # 4 | class ArtSource < Source 5 | 6 | alias_method :old_url, :url 7 | 8 | # @param [String] repo The name of the repository 9 | # 10 | # @param [String] url see {#url} 11 | # 12 | def initialize(repo, url) 13 | super(repo) 14 | @source_url = url 15 | end 16 | 17 | # @return url of this repo 18 | def url 19 | if @source_url 20 | "#{@source_url}" 21 | else 22 | # after super(repo) repo is now the path to the repo 23 | File.read("#{repo}/.artpodrc") if File.exist?("#{dir}/.artpodrc") 24 | end 25 | end 26 | 27 | def git? 28 | false 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/cocoapods_art.rb: -------------------------------------------------------------------------------- 1 | # 2 | # The namespace of the CocoaPods Artifactory plugin. 3 | # 4 | module CocoaPodsArt 5 | VERSION = '1.1.1' 6 | end 7 | -------------------------------------------------------------------------------- /lib/cocoapods_plugin.rb: -------------------------------------------------------------------------------- 1 | require 'pod/command/repo_art' 2 | require 'art_source' 3 | require 'cocoapods-downloader' 4 | require 'cocoapods_art' 5 | 6 | UTIL = Pod::RepoArt::RepoUtil 7 | 8 | Pod::HooksManager.register('cocoapods-art', :source_provider) do |context, options| 9 | Pod::UI.message 'cocoapods-art received source_provider hook' 10 | return unless (sources = options['sources']) 11 | sources.each do |source_name| 12 | source = create_source_from_name(source_name) 13 | context.add_source(source) 14 | end 15 | end 16 | 17 | # @param source_name => name of source incoming from the Podfile configuration 18 | # 19 | # @return [ArtSource] source of the local spec repo which corresponds to to the given name 20 | # 21 | def create_source_from_name(source_name) 22 | art_repo = "#{UTIL.get_repos_art_dir()}/#{source_name}" 23 | repos_dir = Pod::Config.instance.repos_dir 24 | repo = repos_dir + source_name 25 | 26 | Pod::UI.puts "#{art_repo}/.artpodrc\n" 27 | 28 | if File.exist?("#{art_repo}/.artpodrc") 29 | url = File.read("#{art_repo}/.artpodrc") 30 | Pod::ArtSource.new(art_repo, url) 31 | elsif Dir.exist?("#{repo}") 32 | Pod::ArtSource.new(repo, ''); 33 | else 34 | raise Pod::Informative.exception "repo #{source_name} does not exist." 35 | end 36 | end 37 | 38 | # 39 | # This patch is here just so we can pass the -n flag to curl and thus use the ~/.netrc file 40 | # to manage credentials. 41 | # 42 | module Pod 43 | module Downloader 44 | class Http 45 | # Force flattening of index downloads with :indexDownload => true 46 | def self.options 47 | [:type, :flatten, :sha1, :sha256, :indexDownload, :headers] 48 | end 49 | 50 | alias_method :orig_download_file, :download_file 51 | alias_method :orig_should_flatten?, :should_flatten? 52 | 53 | def download_file(full_filename) 54 | parameters = ["-f", "-L", "-o", full_filename, url, "--create-dirs", "--netrc-optional", '--retry', '2'] 55 | parameters << user_agent_argument if headers.nil? || 56 | headers.none? { |header| header.casecmp(USER_AGENT_HEADER).zero? } 57 | 58 | ssl_conf = ["--cert", `git config --global http.sslcert`.gsub("\n", ""), "--key", `git config --global http.sslkey`.gsub("\n", "")] 59 | parameters.concat(ssl_conf) if !ssl_conf.any?(&:blank?) 60 | 61 | netrc_path = ENV["COCOAPODS_ART_NETRC_PATH"] 62 | parameters.concat(["--netrc-file", Pathname.new(netrc_path).expand_path]) if netrc_path 63 | 64 | art_credentials = ENV["COCOAPODS_ART_CREDENTIALS"] 65 | parameters.concat(["--user", art_credentials]) if art_credentials 66 | 67 | winssl_no_revoke = ENV["COCOAPODS_ART_SSL_NO_REVOKE"] 68 | parameters.concat(["--ssl-no-revoke"]) if defined? winssl_no_revoke && "true".casecmp(winssl_no_revoke) 69 | 70 | headers.each do |h| 71 | parameters << '-H' 72 | parameters << h 73 | end unless headers.nil? 74 | 75 | curl! parameters 76 | end 77 | 78 | # Note that we disabled flattening here for the ENTIRE client to deal with 79 | # default flattening for non zip archives messing up tarballs incoming 80 | def should_flatten? 81 | # TODO uncomment when Artifactory stops sending the :flatten flag 82 | # if options.key?(:flatten) 83 | # true 84 | # else 85 | # false 86 | # end 87 | if options.key?(:indexDownload) 88 | true 89 | else 90 | orig_should_flatten? 91 | end 92 | end 93 | end 94 | end 95 | end 96 | 97 | # Override pod's default behavior which is force the master spec repo if 98 | # no sources defined - at this point the plugin sources are not yet fetched from the plugin 99 | # with the source provider hook thus empty Podfiles that only have the plugin declared will 100 | # force a master repo update. 101 | module Pod 102 | class Installer 103 | class Analyzer 104 | 105 | alias_method :orig_sources, :sources 106 | 107 | def sources 108 | if podfile.sources.empty? && podfile.plugins.keys.include?('cocoapods-art') 109 | sources = Array.new 110 | plugin_config = podfile.plugins['cocoapods-art'] 111 | # all sources declared in the plugin clause 112 | plugin_config['sources'].uniq.map do |name| 113 | sources.push(create_source_from_name(name)) 114 | end 115 | @sources = sources 116 | else 117 | orig_sources 118 | end 119 | end 120 | end 121 | end 122 | end 123 | 124 | module Pod 125 | class Source 126 | class Manager 127 | 128 | alias_method :orig_source_from_path, :source_from_path 129 | 130 | # @return [Source] The Source at a given path. 131 | # 132 | # @param [Pathname] path 133 | # The local file path to one podspec repo. 134 | # 135 | def source_from_path(path) 136 | @sources_by_path ||= Hash.new do |hash, key| 137 | art_repo = "#{UTIL.get_repos_art_dir()}/#{key.basename}" 138 | hash[key] = case 139 | when key.basename.to_s == Pod::TrunkSource::TRUNK_REPO_NAME 140 | TrunkSource.new(key) 141 | when (key + '.url').exist? 142 | CDNSource.new(key) 143 | when File.exist?("#{art_repo}/.artpodrc") 144 | create_source_from_name(key.basename) 145 | else 146 | Source.new(key) 147 | end 148 | end 149 | @sources_by_path[path] 150 | end 151 | 152 | end 153 | end 154 | end -------------------------------------------------------------------------------- /lib/pod/artifactory_repo.rb: -------------------------------------------------------------------------------- 1 | module Pod 2 | class ArtifactoryRepo 3 | def initialize(path, url) 4 | @path = path 5 | @url = url 6 | create_name 7 | end 8 | 9 | def create_name 10 | split = @path.split("/") 11 | if split.length > 0 12 | @name = split[split.length - 1] 13 | end 14 | end 15 | 16 | attr_reader :name 17 | attr_reader :path 18 | attr_reader :url 19 | end 20 | end 21 | 22 | 23 | -------------------------------------------------------------------------------- /lib/pod/command/repo_art.rb: -------------------------------------------------------------------------------- 1 | require 'cocoapods_art' 2 | 3 | module Pod 4 | class Command 5 | class RepoArt < Command 6 | require 'pod/command/repo_art/add' 7 | require 'pod/command/repo_art/lint' 8 | require 'pod/command/repo_art/push' 9 | require 'pod/command/repo_art/remove' 10 | require 'pod/command/repo_art/update' 11 | require 'pod/command/repo_art/list' 12 | 13 | UTIL = Pod::RepoArt::RepoUtil 14 | 15 | self.abstract_command = true 16 | self.version = CocoaPodsArt::VERSION 17 | self.description = 'Enables working with JFrog Artifactory as a Specs repo and as a repository for Pods.'\ 18 | "\n v#{CocoaPodsArt::VERSION}\n" 19 | self.summary = <<-SUMMARY 20 | Artifactory support for CocoaPods 21 | SUMMARY 22 | 23 | self.default_subcommand = 'list' 24 | 25 | def init 26 | @repos_art_dir = UTIL.get_repos_art_dir() 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/pod/command/repo_art/add.rb: -------------------------------------------------------------------------------- 1 | require 'util/repo_util' 2 | 3 | module Pod 4 | class Command 5 | class RepoArt 6 | class Add < RepoArt 7 | UTIL = Pod::RepoArt::RepoUtil 8 | 9 | self.summary = 'Add a Specs repo from Artifactory.' 10 | 11 | self.description = <<-DESC 12 | Retrieves the index from an Artifactory instance at 'URL' to the local spec repos-art 13 | directory at `~/.cocoapods/repos-art/'NAME'`. 14 | DESC 15 | 16 | self.arguments = [ 17 | CLAide::Argument.new('NAME', true), 18 | CLAide::Argument.new('URL', true) 19 | ] 20 | 21 | def initialize(argv) 22 | init 23 | @name, @url = argv.shift_argument, argv.shift_argument 24 | @silent = argv.flag?('silent', false) 25 | super 26 | end 27 | 28 | def validate! 29 | super 30 | unless @name && @url 31 | help! 'This command requires both a repo name and a url.' 32 | end 33 | end 34 | 35 | def run 36 | UI.section("Retrieving index from `#{@url}` into local spec repo `#{@name}`") do 37 | # Check if a repo with the same name under repos/ already exists 38 | repos_path = "#{Pod::Config.instance.home_dir}/repos" 39 | raise Informative, "Path #{repos_path}/#{@name} already exists - remove it first, "\ 40 | "or run 'pod repo-art update #{@name}' to update it" if File.exist?("#{repos_path}/#{@name}") && !@silent 41 | 42 | # Check if a repo with the same name under repo-art/ already exists 43 | repo_dir_root = "#{@repos_art_dir}/#{@name}" 44 | raise Informative, "Path #{repo_dir_root} already exists - remove it first, "\ 45 | "or run 'pod repo-art update #{@name}' to update it" if File.exist?(repo_dir_root) && !@silent 46 | 47 | FileUtils::mkdir_p repo_dir_root 48 | 49 | repo_dir_specs = "#{repo_dir_root}/Specs" 50 | begin 51 | downloader = Pod::Downloader::Http.new(repo_dir_specs, "#{@url}/index/fetchIndex", :type => 'tgz', :indexDownload => true) 52 | downloader.download 53 | rescue => e 54 | FileUtils.remove_entry_secure(repo_dir_root, :force => true) 55 | raise Informative, "Error getting the index from Artifactory at: '#{@url}' : #{e.message}" 56 | end 57 | 58 | begin 59 | UTIL.cleanup_index_download(repo_dir_specs) 60 | UTIL.del_redundant_spec_dir("#{repo_dir_specs}/Specs") 61 | rescue => e 62 | UI.warn("Failed cleaning up temp files in #{repo_dir_specs}") 63 | end 64 | 65 | begin 66 | artpodrc_path = create_artpodrc_file(repo_dir_root) 67 | rescue => e 68 | raise Informative, "Cannot create file '#{artpodrc_path}' because : #{e.message}."\ 69 | '- your Artifactory-backed Specs repo will not work correctly without it!' 70 | end 71 | # Create a local git repository in the newly added Artifactory local repo 72 | system "cd '#{repo_dir_root}' && git init && git add . && git commit -m 'Artifactory repo init'" 73 | 74 | # Create local repo under repos/ which is a remote for the new local git repository 75 | system "cd '#{repos_path}' && git clone file://#{repo_dir_root}" 76 | end 77 | UI.puts "Successfully added repo #{@name}".green unless @silent 78 | end 79 | 80 | # Creates the .artpodrc file which contains the repository's url in the root of the Spec repo 81 | # 82 | # @param [String] repo_dir_root root of the Spec repo 83 | # 84 | def create_artpodrc_file(repo_dir_root) 85 | artpodrc_path = "#{repo_dir_root}/.artpodrc" 86 | artpodrc = File.new(artpodrc_path, "wb") 87 | artpodrc << @url 88 | artpodrc.close 89 | artpodrc_path 90 | end 91 | 92 | end 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/pod/command/repo_art/lint.rb: -------------------------------------------------------------------------------- 1 | module Pod 2 | class Command 3 | class RepoArt 4 | class Lint < RepoArt 5 | self.summary = 'Validates all specs in a repo.' 6 | 7 | self.description = <<-DESC 8 | Lints the spec-repo `NAME`. If a directory is provided it is assumed 9 | to be the root of a repo. Finally, if `NAME` is not provided this 10 | will lint all the Artifactory-backed spec-repos known to CocoaPods. 11 | DESC 12 | 13 | self.arguments = [ 14 | CLAide::Argument.new(%w(NAME DIRECTORY), true) 15 | ] 16 | 17 | def self.options 18 | [ 19 | ['--only-errors', 'Lint presents only the errors'] 20 | ].concat(super) 21 | end 22 | 23 | def initialize(argv) 24 | init 25 | @name = argv.shift_argument 26 | @only_errors = argv.flag?('only-errors') 27 | super 28 | end 29 | 30 | def run 31 | repos = if @name 32 | UTIL.get_art_repo(@name) 33 | else 34 | UTIL.get_art_repos 35 | end 36 | 37 | repos.each do |repo| 38 | UI.puts "\nLinting spec repo `#{repo.name}`\n".yellow 39 | 40 | validator = Source::HealthReporter.new(repo.path) 41 | validator.pre_check do |_name, _version| 42 | UI.print '.' 43 | end 44 | report = validator.analyze 45 | UI.puts 46 | UI.puts 47 | 48 | report.pods_by_warning.each do |message, versions_by_name| 49 | UI.puts "-> #{message}".yellow 50 | versions_by_name.each { |name, versions| UI.puts " - #{name} (#{versions * ', '})" } 51 | UI.puts 52 | end 53 | 54 | report.pods_by_error.each do |message, versions_by_name| 55 | UI.puts "-> #{message}".red 56 | versions_by_name.each { |name, versions| UI.puts " - #{name} (#{versions * ', '})" } 57 | UI.puts 58 | end 59 | 60 | UI.puts "Analyzed #{report.analyzed_paths.count} podspecs files.\n\n" 61 | if report.pods_by_error.count.zero? 62 | UI.puts 'All the specs passed validation.'.green << "\n\n" 63 | else 64 | raise Informative, "#{report.pods_by_error.count} podspecs failed validation." 65 | end 66 | end 67 | end 68 | 69 | end 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/pod/command/repo_art/list.rb: -------------------------------------------------------------------------------- 1 | require 'util/repo_util' 2 | require 'pod/artifactory_repo' 3 | 4 | module Pod 5 | class Command 6 | class RepoArt 7 | class List < RepoArt 8 | 9 | UTIL = Pod::RepoArt::RepoUtil 10 | 11 | self.summary = 'List Artifactory-backed repos.' 12 | 13 | self.description = <<-DESC 14 | List the Artifactory repos from the local spec-repos directory at `~/.cocoapods/repos-art/.` 15 | DESC 16 | 17 | def self.options 18 | [ 19 | ['--count-only', 'Show the total number of repos'] 20 | ].concat(super) 21 | end 22 | 23 | def initialize(argv) 24 | init 25 | @count_only = argv.flag?('count-only') 26 | super 27 | end 28 | 29 | def run 30 | repos = UTIL.get_art_repos 31 | print_art_repos(repos) unless @count_only 32 | print_art_repos_count(repos) 33 | end 34 | 35 | def print_art_repos(repos) 36 | for repo in repos 37 | UI.title repo.name do 38 | UI.puts "- URL: #{repo.url}" 39 | UI.puts "- Path: #{repo.path}" 40 | end 41 | end 42 | UI.puts "\n" 43 | end 44 | 45 | def print_art_repos_count(repos) 46 | number_of_repos = repos.length 47 | repo_string = number_of_repos != 1 ? 'repos' : 'repo' 48 | UI.puts "#{number_of_repos} #{repo_string}\n".green 49 | end 50 | 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/pod/command/repo_art/push.rb: -------------------------------------------------------------------------------- 1 | require 'util/repo_util' 2 | 3 | module Pod 4 | class Command 5 | class RepoArt 6 | class Push < RepoArt 7 | UTIL = Pod::RepoArt::RepoUtil 8 | 9 | extend Executable 10 | executable :curl 11 | 12 | self.summary = 'Push a spec to Artifactory' 13 | 14 | self.description = <<-DESC 15 | Creates a directory and version folder for the pod in the local copy of `REPO` (~/.cocoapods/repos/[REPO]), 16 | copies the podspec file into the version directory, and finally pushes the spec to Artifactory 17 | DESC 18 | 19 | self.arguments = [ 20 | CLAide::Argument.new('REPO', true), 21 | CLAide::Argument.new('NAME.podspec', false) 22 | ] 23 | 24 | def self.options 25 | [ 26 | ['--local-only', 'Does not push changes to Artifactory'] 27 | ].concat(super) 28 | end 29 | 30 | def initialize(argv) 31 | @local_only = argv.flag?('local-only') 32 | @repo = argv.shift_argument 33 | @podspec = argv.shift_argument 34 | super 35 | end 36 | 37 | def validate! 38 | super 39 | help! 'A spec-repo name is required.' unless @repo 40 | end 41 | 42 | def run 43 | # update_repo 44 | add_specs_to_repo 45 | end 46 | 47 | # Updates the local repo against the Artifactory backend 48 | # 49 | # def update_repo 50 | # argv = CLAide::ARGV.new([@repo]) 51 | # update = Command::RepoArt::Update.new(argv) 52 | # update.run 53 | # end 54 | 55 | # Adds the specs to the local repo and pushes them to Artifactory if required 56 | # 57 | def add_specs_to_repo 58 | UI.puts "Adding the #{'spec'.pluralize(podspec_files.count)} to repo `#{@repo}'\n" 59 | podspec_files.each do |spec_file| 60 | spec = Pod::Specification.from_file(spec_file) 61 | output_path = File.join(repo_specs_dir, spec.name, spec.version.to_s) 62 | UI.puts " --> #{get_message(output_path, spec)}" 63 | if @local_only 64 | create_json_in_path(output_path, spec) 65 | else 66 | # TODO push to local disabled until virtual repo support 67 | raise Informative, 'Pushing specs to Artifactory is currently disabled' 68 | =begin 69 | begin 70 | podspec_json_tmp_path = create_json_in_path(output_path, spec) 71 | rescue => e 72 | FileUtils.remove(output_path, :force => true) 73 | raise Informative, "Error writing spec file in target path '#{output_path}': #{e.message}" 74 | end 75 | 76 | begin 77 | push_to_remote(spec, podspec_json_tmp_path) 78 | rescue => e 79 | FileUtils.remove(output_path, :force => true) 80 | raise Informative, "Error pushing to remote '#{@repo}': #{e.message}" 81 | end 82 | FileUtils.remove(podspec_json_tmp_path, :force => true) 83 | =end 84 | end 85 | end 86 | end 87 | 88 | private 89 | 90 | # Creates an op information message based on what's being done 91 | # 92 | # @param [Pathname] output_path path where the spec will be written 93 | # 94 | # @param [Specification] spec the spec 95 | # 96 | def get_message(output_path, spec) 97 | if Pathname.new(output_path).exist? 98 | message = "[Fix] #{spec}" 99 | elsif Pathname.new(File.join(repo_specs_dir, spec.name)).exist? 100 | message = "[Update] #{spec}" 101 | else 102 | message = "[Add] #{spec}" 103 | end 104 | message 105 | end 106 | 107 | # @param [Pathname] output_path path where to create json spec 108 | # 109 | # @param [Specification] spec to write 110 | # 111 | # @return [String] path where the json was written 112 | # 113 | def create_json_in_path(output_path, spec) 114 | url = UTIL.get_art_url(repo_root_dir) 115 | FileUtils.mkdir_p(output_path) 116 | podspec_json_path = "#{output_path}/#{spec.name}.podspec.json" 117 | if @local_only 118 | podspec_json = File.new(podspec_json_path, "wb") 119 | podspec_json.puts(spec.to_pretty_json) 120 | podspec_json.close 121 | podspec_json_path 122 | else 123 | podspec_json_tmp_path = "#{output_path}/#{spec.name}.podspec.json.tmp" 124 | FileUtils.remove(podspec_json_path, :force => true) 125 | podspec_json_temp = File.new(podspec_json_tmp_path, "wb") 126 | podspec_json_temp.puts(spec.to_pretty_json) 127 | podspec_json_temp.close 128 | curl! '-XPOST', "#{url}/index/modifySpec/#{spec.name}/#{spec.version.to_s}", '-n', '-L', '-H', '"Content-Type:application/json"', '-T', "#{podspec_json_tmp_path}", '-o', podspec_json_path, '--create-dirs' 129 | podspec_json_tmp_path 130 | end 131 | end 132 | 133 | # @param [Specification] spec the spec 134 | # 135 | def push_to_remote(spec, podspec_json_tmp_path) 136 | UI.puts 'Pushing index to Artifactory' 137 | url = UTIL.get_art_url(repo_root_dir) 138 | begin 139 | curl! '-XPUT', "#{url}/index/pushSpec/#{spec.name}/#{spec.version.to_s}", '-n', '-f', '-L', '-H', '"Content-Type:application/json"', '-T', "#{podspec_json_tmp_path}" 140 | rescue => e 141 | raise Informative, "Error pushing spec to Artifactory: #{e.message}" 142 | end 143 | UI.puts "Spec #{spec.name}-#{spec.version.to_s} pushed successfully to Artifactory".green 144 | end 145 | 146 | # @return [Array] The path of the specifications to push. 147 | # 148 | def podspec_files 149 | if @podspec 150 | path = Pathname(@podspec) 151 | raise Informative, "Couldn't find #{@podspec}" unless path.exist? 152 | [path] 153 | else 154 | files = Pathname.glob('*.podspec{,.json}') 155 | raise Informative, "Couldn't find any podspec files in current directory" if files.empty? 156 | files 157 | end 158 | end 159 | 160 | # @return [Pathname] The Specs directory of the repository. 161 | # 162 | def repo_specs_dir 163 | root_dir = config.repos_dir + @repo 164 | specs_dir = Pathname.new(File.join(root_dir, 'Specs')) 165 | raise Informative, "'#{@repo}' is not an Artifactory-backed Specs repo" unless UTIL.art_repo?(root_dir) 166 | raise Informative, "Specs dir of repo `#{@repo}` not found in #{specs_dir}" unless File.exist?(specs_dir) 167 | specs_dir 168 | end 169 | 170 | # @return [Pathname] The root directory of the repository. 171 | # 172 | def repo_root_dir 173 | root_dir = config.repos_dir + @repo 174 | raise Informative, "'#{@repo}' is not an Artifactory-backed Specs repo" unless UTIL.art_repo?(root_dir) 175 | root_dir 176 | end 177 | 178 | end 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /lib/pod/command/repo_art/remove.rb: -------------------------------------------------------------------------------- 1 | module Pod 2 | class Command 3 | class RepoArt 4 | class Remove < RepoArt 5 | self.summary = 'Remove an Artifactory-backed Specs repo' 6 | 7 | self.description = <<-DESC 8 | Deletes the Spec repo called 'NAME' from the local spec-repos directory at '~/.cocoapods/repos-art/.' 9 | DESC 10 | 11 | self.arguments = [ 12 | CLAide::Argument.new('NAME', true) 13 | ] 14 | 15 | def initialize(argv) 16 | init 17 | @name = argv.shift_argument 18 | super 19 | end 20 | 21 | def validate! 22 | super 23 | help! 'Deleting a repo needs a `NAME`.' unless @name 24 | help! "repo #{@name} does not exist" unless File.directory?(repo_dir_root) 25 | help! "You do not have permission to delete the #{@name} repository." \ 26 | 'Perhaps try prefixing this command with sudo.' unless File.writable?(repo_dir_root) 27 | end 28 | 29 | def run 30 | UI.section("Removing spec repo `#{@name}`") do 31 | FileUtils.rm_rf(repo_dir_root) 32 | end 33 | end 34 | 35 | def repo_dir_root 36 | "#{@repos_art_dir}/#{@name}" 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/pod/command/repo_art/update.rb: -------------------------------------------------------------------------------- 1 | require 'util/repo_util' 2 | 3 | module Pod 4 | class Command 5 | class RepoArt 6 | class Update < RepoArt 7 | UTIL = Pod::RepoArt::RepoUtil 8 | 9 | self.summary = 'Update an Artifactory-backed Specs repo.' 10 | 11 | self.description = <<-DESC 12 | Updates the Artifactory-backed spec-repo `NAME`. 13 | DESC 14 | 15 | def self.options 16 | [ 17 | ['--prune', 'Prunes entries which do not exist in the remote this index was pulled from.'] 18 | ].concat(super) 19 | end 20 | 21 | self.arguments = [ 22 | CLAide::Argument.new('NAME', true) 23 | ] 24 | 25 | def initialize(argv) 26 | @name = argv.shift_argument 27 | @prune = argv.flag?('prune', false) 28 | super 29 | end 30 | 31 | def validate! 32 | super 33 | unless @name 34 | help! 'This command requires a repo name to run.' 35 | end 36 | end 37 | 38 | def run 39 | update(@name, true) 40 | end 41 | 42 | private 43 | 44 | # Update command for Artifactory sources. 45 | # 46 | # @param [String] source_name name 47 | # 48 | def update(source_name = nil, show_output = false) 49 | if source_name 50 | sources = [UTIL.get_art_repo(source_name)] 51 | else 52 | sources = UTIL.get_art_repos() 53 | end 54 | 55 | sources.each do |source| 56 | UI.section "Updating spec repo `#{source.name}`" do 57 | Dir.chdir(source.path) do 58 | begin 59 | # TODO HEAD to api/updateTime 60 | # TODO unless .lastupdated >= api/updateTime do 61 | # TODO Until we support delta downloads, update is actually add if not currently up tp date 62 | url = UTIL.get_art_url(source.path) 63 | if @prune 64 | hard_update(source.name, source.path, url) 65 | else 66 | soft_update(source.path, url) 67 | end 68 | UI.puts "Successfully updated repo #{source.name}".green if show_output && !config.verbose? 69 | rescue => e 70 | UI.warn "Unable to update repo `#{source.name}`: #{e.message}" 71 | end 72 | end 73 | end 74 | end 75 | end 76 | 77 | # Performs a 'soft' update which appends any changes from the remote without deleting out-of-sync entries 78 | # 79 | def soft_update(path, url) 80 | downloader = Pod::Downloader::Http.new("#{path}", "#{url}/index/fetchIndex", :type => 'tgz', :indexDownload => true) 81 | downloader.download 82 | UTIL.cleanup_index_download("#{path}") 83 | UTIL.del_redundant_spec_dir("#{path}/Specs/Specs") 84 | system "cd '#{path}' && git add . && git commit -m 'Artifactory repo update specs'" 85 | end 86 | 87 | # Performs a 'hard' update which prunes all index entries which are not sync with the remote (override) 88 | # 89 | def hard_update(name, path, url) 90 | UI.puts path 91 | begin 92 | repos_path = "#{Pod::Config.instance.home_dir}/repos/#{name}" 93 | repos_art_path = "#{Pod::Config.instance.home_dir}/repos-art/#{name}" 94 | 95 | repo_update_tmp = "#{repos_path}_update_tmp" 96 | repo_art_update_tmp = "#{repos_art_path}_update_tmp" 97 | 98 | system("mv", repos_path.to_s, repo_update_tmp) 99 | system("mv", repos_art_path.to_s, repo_art_update_tmp) 100 | 101 | argv = CLAide::ARGV.new([name, url, '--silent']) 102 | Pod::Command::RepoArt::Add.new(argv).run 103 | 104 | FileUtils.remove_entry_secure(repo_update_tmp, :force => true) 105 | FileUtils.remove_entry_secure(repo_art_update_tmp, :force => true) 106 | rescue => e 107 | FileUtils.remove_entry_secure(path.to_s, :force => true) 108 | system("mv", repo_update_tmp, repos_path.to_s) 109 | system("mv", repo_art_update_tmp, repos_art_path.to_s) 110 | raise Informative, "Error getting the index from Artifactory at: '#{url}' : #{e.message}" 111 | end 112 | end 113 | end 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /lib/util/repo_util.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | module Pod 4 | class RepoArt 5 | class RepoUtil 6 | 7 | # @return list of Artifactory repos, read from the ~/.cocoapods/repos-art 8 | # 9 | def self.get_art_repos 10 | repos_art_dir = UTIL.get_repos_art_dir() 11 | dirs = Dir.glob "#{repos_art_dir}/*/" 12 | repos = [] 13 | for dir in dirs 14 | if UTIL.artpodrc_file_exists(dir) 15 | url = UTIL.get_art_url(dir) 16 | repos.push ArtifactoryRepo.new(dir, url) 17 | end 18 | end 19 | repos 20 | end 21 | 22 | # @return [Source] The Artifactory source with the given name. 23 | # 24 | # @param [String] name The name of the source. 25 | # 26 | def self.get_art_repo(name) 27 | #specified_source = Pod::Config.instance.sources_manager.aggregate.sources.find { |s| s.name == name } 28 | repos = get_art_repos() 29 | art_repo = nil 30 | for repo in repos 31 | if repo.name == name 32 | art_repo = repo 33 | end 34 | end 35 | 36 | unless art_repo 37 | raise Informative, "Unable to find the Artifactory-backed repo called `#{name}`." 38 | end 39 | art_repo 40 | end 41 | 42 | # @return whether a source is an Artifactory backed repo. 43 | # 44 | # @param [Pathname] repo_root_path root directory of the repo. 45 | # 46 | def self.art_repo?(repo_root_path) 47 | true if File.exist?("#{repo_root_path}/.artpodrc") 48 | end 49 | 50 | # @return the url of this Artifactory repo which is stored in the .artpodrc file in it's root 51 | # 52 | # @param [Pathname] repo_root_path root directory of the repo. 53 | # 54 | def self.get_art_url(repo_root_path) 55 | File.read("#{repo_root_path}/.artpodrc") 56 | end 57 | 58 | # @return if the .artpodrc file exists in the given dir 59 | # 60 | # @param [Pathname] dir root directory of the repo. 61 | # 62 | def self.artpodrc_file_exists(dir) 63 | File.exist?("#{dir}/.artpodrc") 64 | end 65 | 66 | # @return the full path to the repos-art directory 67 | # 68 | def self.get_repos_art_dir() 69 | "#{Pod::Config.instance.home_dir}/repos-art" 70 | end 71 | 72 | # Cleans up all of the junk left over from using the Downloader 73 | # 74 | def self.cleanup_index_download(tmp_file_dir) 75 | # The downloader names every file it gets file. 76 | temp_file = "#{tmp_file_dir}/file.tgz" 77 | File.delete(temp_file) if File.exist?(temp_file) 78 | end 79 | 80 | def self.del_redundant_spec_dir(redundant_specs_dir) 81 | # The default flattening the Downloader uses for tgz makes this screwy 82 | Dir.delete(redundant_specs_dir) if (Dir.exist?(redundant_specs_dir) && Dir.glob(redundant_specs_dir + '/' + '*').empty?) 83 | end 84 | end 85 | end 86 | end 87 | 88 | --------------------------------------------------------------------------------