diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..52876ee --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,13 @@ +name: CI +on: + push: + tags: + - v* +jobs: + build: + runs-on: macos-latest + steps: + - name: Publish gem + uses: dawidd6/action-publish-gem@v1.1.0 + with: + api_key: ${{secrets.RUBYGEMS_API_KEY}} \ No newline at end of file diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..cac422a --- /dev/null +++ b/Gemfile @@ -0,0 +1,10 @@ +source 'https://rubygems.org' + +gemspec + +group :development do + gem 'cocoapods' + gem 'cocoapods-core' + gem 'cocoapods-downloader' + gem 'claide' +end \ No newline at end of file diff --git a/LICENSE b/LICENSE index 1aa9adf..19589b7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,22 @@ -MIT License +Copyright (c) 2020 mrdaios -Copyright (c) 2020 Mrdaios +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index d46b1d1..97bf548 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ # cocoapods-nexus -a plugin for nexus + +A description of cocoapods-nexus. + +## Installation + + $ gem install cocoapods-nexus + +## Usage + + $ pod spec nexus POD_NAME diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..c34b828 --- /dev/null +++ b/Rakefile @@ -0,0 +1,13 @@ +require 'bundler/gem_tasks' + +def specs(dir) + FileList["spec/#{dir}/*_spec.rb"].shuffle.join(' ') +end + +desc 'Runs all the specs' +task :specs do + sh "bundle exec bacon #{specs('**')}" +end + +task :default => :specs + diff --git a/cocoapods-nexus.gemspec b/cocoapods-nexus.gemspec new file mode 100644 index 0000000..1fb5533 --- /dev/null +++ b/cocoapods-nexus.gemspec @@ -0,0 +1,26 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + +Gem::Specification.new do |spec| + spec.name = 'cocoapods-nexus' + spec.version = "0.0.1" + spec.authors = ['mrdaios'] + spec.email = ['mrdaios@gmail.com'] + spec.description = 'a cocoapods plugin for nexus.' + spec.summary = '运行时修改spec的source为nexus中的预编译的zip.' + spec.homepage = 'https://github.com/mrdaios/cocoapods-nexus' + spec.license = 'MIT' + + spec.files = `git ls-files`.split($/) + # spec.files = Dir['lib/**/*'] + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = ['lib'] + + spec.add_runtime_dependency 'cocoapods', '>= 1.9.3' + spec.add_runtime_dependency 'rest-client', '~> 2.1.0' + + spec.add_development_dependency 'bundler', '~> 1.3' + spec.add_development_dependency 'rake', '~> 13.0' +end diff --git a/lib/cocoapods-nexus.rb b/lib/cocoapods-nexus.rb new file mode 100644 index 0000000..5f5f0e1 --- /dev/null +++ b/lib/cocoapods-nexus.rb @@ -0,0 +1,3 @@ +require 'cocoapods-nexus/command' +require 'cocoapods-nexus/hook' +require 'cocoapods-nexus/nexus_source' \ No newline at end of file diff --git a/lib/cocoapods-nexus/api.rb b/lib/cocoapods-nexus/api.rb new file mode 100644 index 0000000..5fe5f5f --- /dev/null +++ b/lib/cocoapods-nexus/api.rb @@ -0,0 +1,16 @@ +require 'cocoapods-nexus/api/connection' +require 'cocoapods-nexus/api/components' + +module CocoapodsNexus + # https://github.com/Cisco-AMP/nexus_api + class API + attr_accessor :connection + attr_accessor :repo + + # 用于管理nexus服务器 + def initialize(hostname:, repo:) + @connection = CocoapodsNexus::API::NexusConnection.new(hostname: hostname) + @repo = repo + end + end +end \ No newline at end of file diff --git a/lib/cocoapods-nexus/api/components.rb b/lib/cocoapods-nexus/api/components.rb new file mode 100644 index 0000000..5617bcd --- /dev/null +++ b/lib/cocoapods-nexus/api/components.rb @@ -0,0 +1,29 @@ +module CocoapodsNexus + class API + # 搜索组件 + def search_maven_component(artifact_id:) + @connection.get_response(endpoint: "search?repository=#{@repo}&name=#{artifact_id}") + end + + # 上传组件 + def upload_maven_component(artifact_id:, version:, group_id:, podspec:, artifact:, files:) + parameters = { + 'maven2.artifactId' => artifact_id, + 'maven2.version' => version, + 'maven2.groupId' => group_id, + 'maven2.generate-pom' => true, + 'maven2.packaging' => artifact.nil? ? 'podspec' : File.extname(artifact).delete(".") + } + upload_files = [] + upload_files << podspec unless podspec.nil? + upload_files << artifact unless artifact.nil? + upload_files | files unless files.nil? + + upload_files.each_index do |index| + parameters["maven2.asset#{index + 1}"] = File.open(upload_files[index], 'r:utf-8') + parameters["maven2.asset#{index + 1}.extension"] = File.extname(upload_files[index]).delete(".") + end + @connection.post(endpoint: "components?repository=#{@repo}", parameters: parameters, headers: {}) + end + end +end diff --git a/lib/cocoapods-nexus/api/connection.rb b/lib/cocoapods-nexus/api/connection.rb new file mode 100644 index 0000000..681ac0a --- /dev/null +++ b/lib/cocoapods-nexus/api/connection.rb @@ -0,0 +1,158 @@ +require 'base64' +require 'json' +require 'rest-client' +require 'uri' + +module CocoapodsNexus + class API + class NexusConnection + VALID_RESPONSE_CODES = [200, 201, 204].freeze + + attr_accessor :continuation_token + + def initialize(hostname:) + @hostname = hostname + end + + def get_response(endpoint:, paginate: false, headers: {'Content-Type' => 'application/json'}, api_version: 'v1') + response = send_get(endpoint, paginate, headers, api_version) + response.nil? ? {} : jsonize(response) + end + + def get(endpoint:, paginate: false, headers: {'Content-Type' => 'application/json'}, api_version: 'v1') + valid?(send_get(endpoint, paginate, headers, api_version)) + end + + def post(endpoint:, parameters: '', headers: {'Content-Type' => 'application/json'}, api_version: 'v1') + response = send_request( + :post, + endpoint, + parameters: parameters, + headers: headers, + api_version: api_version + ) + valid?(response) + end + + def put(endpoint:, parameters: '', headers: {'Content-Type' => 'application/json'}, api_version: 'v1') + response = send_request( + :put, + endpoint, + parameters: parameters, + headers: headers, + api_version: api_version + ) + valid?(response) + end + + def delete(endpoint:, headers: {'Content-Type' => 'application/json'}, api_version: 'v1') + response = send_request( + :delete, + endpoint, + headers: headers, + api_version: api_version + ) + valid?(response) + end + + def head(asset_url:) + catch_connection_error do + RestClient.head(URI.escape(asset_url)) + end + end + + def content_length(asset_url:) + response = head(asset_url: asset_url) + return -1 unless response.respond_to?(:headers) + response.headers[:content_length] + end + + def download(url:) + catch_connection_error do + RestClient.get(URI.escape(url), authorization_header) + end + end + + def paginate? + !@continuation_token.nil? + end + + + private + + def valid?(response) + return false if response.nil? + VALID_RESPONSE_CODES.include?(response.code) ? true : false + end + + def handle(error) + puts 'ERROR: Request failed' + puts error + end + + def catch_connection_error + begin + yield + rescue SocketError => error + return handle(error) + rescue RestClient::Unauthorized => error + return handle(error) + rescue RestClient::Exceptions::ReadTimeout => error + return handle(error) + rescue RestClient::ExceptionWithResponse => error + return handle(error.response) + rescue StandardError => error + return handle(error) + end + end + + def authorization_header + {} + end + + def send_request(connection_method, endpoint, parameters: '', headers: {}, api_version: 'v1') + parameters = parameters.to_json if headers['Content-Type'] == 'application/json' + url = "#{@hostname}/nexus/service/rest/#{api_version}/#{endpoint}" + catch_connection_error do + RestClient::Request.execute( + method: connection_method, + url: URI.escape(url), + payload: parameters, + headers: authorization_header.merge(headers) + ) + end + end + + def send_get(endpoint, paginate, headers, api_version) + url_marker = endpoint.include?('?') ? '&' : '?' + # paginate answers is the user requesting pagination, paginate? answers does a continuation token exist + # if an empty continuation token is included in the request we'll get an ArrayIndexOutOfBoundsException + endpoint += "#{url_marker}continuationToken=#{@continuation_token}" if paginate && paginate? + send_request( + :get, + endpoint, + headers: headers, + api_version: api_version + ) + end + + # That's right, nexus has inconsistent null values for its api + def continuation_token_for(json) + return nil if json['continuationToken'].nil? + return nil if json['continuationToken'] == 'nil' + json['continuationToken'] + end + + def jsonize(response) + json = JSON.parse(response.body) + if json.class == Hash + @continuation_token = continuation_token_for(json) + json = json['items'] if json['items'] + end + json + rescue JSON::ParserError + response.body + end + end + end +end diff --git a/lib/cocoapods-nexus/command.rb b/lib/cocoapods-nexus/command.rb new file mode 100644 index 0000000..f8ebc74 --- /dev/null +++ b/lib/cocoapods-nexus/command.rb @@ -0,0 +1 @@ +require 'cocoapods-nexus/command/nexus' diff --git a/lib/cocoapods-nexus/command/nexus.rb b/lib/cocoapods-nexus/command/nexus.rb new file mode 100644 index 0000000..9fee7e4 --- /dev/null +++ b/lib/cocoapods-nexus/command/nexus.rb @@ -0,0 +1,24 @@ +require 'cocoapods-nexus/nexus_source' + +module Pod + class Command + class Nexus < Command + require 'cocoapods-nexus/command/nexus/add' + require 'cocoapods-nexus/command/nexus/list' + require 'cocoapods-nexus/command/nexus/push' + + self.abstract_command = true + + self.summary = 'a cocoapods plugin for nexus' + + self.description = <<-DESC + a cocoapods plugin for nexus. + DESC + + def initialize(argv) + @repos_nexus_dir = Pod::Config.instance.repos_dir + super + end + end + end +end diff --git a/lib/cocoapods-nexus/command/nexus/add.rb b/lib/cocoapods-nexus/command/nexus/add.rb new file mode 100644 index 0000000..efc6bc3 --- /dev/null +++ b/lib/cocoapods-nexus/command/nexus/add.rb @@ -0,0 +1,58 @@ +require 'fileutils' + +module Pod + class Command + class Nexus + class Add < Nexus + self.summary = 'Add a nexus repo.' + + self.description = <<-DESC + 添加一个nexus的repo + DESC + + self.arguments = [ + CLAide::Argument.new('NAME', true), + CLAide::Argument.new('URL', true) + ] + + def initialize(argv) + @name = argv.shift_argument + @url = argv.shift_argument + @silent = argv.flag?('silent', false) + @silent = false + super + end + + def validate! + super + help! '需要配置`NAME`和`URL`.' unless @name && @url + end + + def run + UI.section("从#{@url}添加#{@name}仓库") do + repos_path = File.join(@repos_nexus_dir, @name) + raise Pod::Informative.exception "#{repos_path}已经存在. 请删除或者执行'pod nexus add #{@name} #{@url}'" if File.exist?(repos_path) && !@silent + repo_dir_root = "#{@repos_nexus_dir}/#{@name}" + + FileUtils.mkdir_p repo_dir_root + + begin + nexus_path = create_nexus_file(repo_dir_root) + rescue StandardError => e + raise Informative, "Cannot create file '#{nexus_path}' because : #{e.message}." + end + UI.puts "Successfully added repo #{@name}".green unless @silent + end + end + + def create_nexus_file(repo_dir_root) + nexus_path = "#{repo_dir_root}/.nexus" + nexus_path = File.new(nexus_path, 'wb') + nexus_path << @url + nexus_path.close + nexus_path + end + end + end + end +end diff --git a/lib/cocoapods-nexus/command/nexus/list.rb b/lib/cocoapods-nexus/command/nexus/list.rb new file mode 100644 index 0000000..d456b62 --- /dev/null +++ b/lib/cocoapods-nexus/command/nexus/list.rb @@ -0,0 +1,34 @@ +require 'fileutils' +require 'cocoapods-nexus/nexus_source' + +module Pod + class Command + class Nexus + class List < Nexus + self.summary = 'Add a nexus repo.' + + self.description = <<-DESC + 添加一个nexus的repo + DESC + + def run + repos_dir = Pod::Config.instance.repos_dir + dirs = Dir.glob "#{repos_dir}/*/" + repos = [] + dirs.each do |dir| + next unless File.exist?("#{dir}/.nexus") + url = File.read("#{dir}/.nexus") + repos.push Pod::NexusSource.new(dir, url) if url + end + + repos.each { |repo| + UI.title repo.name do + UI.puts "- URL: #{repo.url}" + UI.puts "- Path: #{repo.repo}" + end + } + end + end + end + end +end diff --git a/lib/cocoapods-nexus/command/nexus/push.rb b/lib/cocoapods-nexus/command/nexus/push.rb new file mode 100644 index 0000000..a89ce13 --- /dev/null +++ b/lib/cocoapods-nexus/command/nexus/push.rb @@ -0,0 +1,71 @@ +require 'cocoapods-nexus/api' + +module Pod + class Command + class Nexus + class Push < Nexus + self.summary = 'Push a podspec.' + + self.description = <<-DESC + Push a podspec to nexus. + DESC + + self.arguments = [ + CLAide::Argument.new('NAME.podspec', true) + ] + + def self.options + [ + ['--url=url', 'a nexus hostname'], + ['--repo=repo', 'a nexus repo'], + ['--artifact=artifact', 'a nexus artifact'] + ].concat(super) + end + + def initialize(argv) + @podspec = argv.shift_argument + @url = argv.option('url') + @repo = argv.option('repo') + @artifact = argv.option('artifact') + super + end + + def validate! + super + help! 'A podspec is required.' unless @podspec && File.exist?(File.expand_path(@podspec)) + help! 'A url is required.' unless @url + help! 'A repo is required.' unless @repo + end + + def run + podspec_path = File.expand_path(@podspec) + artifact_path = File.expand_path(@artifact) unless @artifact.nil? + + UI.section("开始发布 #{File.basename(@podspec)} -> #{@url}/nexus/#browse/browse:#{@repo}") do + spec = Specification.from_file(podspec_path) + artifact_id = spec.attributes_hash['name'] + version = spec.attributes_hash['version'] + group_id = 'Specs' + + if nexus_api.upload_maven_component(artifact_id: artifact_id, + version: version, + group_id: group_id, + podspec: podspec_path, + artifact: artifact_path, + files: []) + UI.puts "成功发布 #{artifact_id}(#{version})" + else + raise Informative, "发布失败 #{artifact_id}(#{version}),请检查~/.netrc文件或#{@repo}类型" + end + end + end + + private + + def nexus_api + @nexus_api ||= CocoapodsNexus::API.new(hostname: @url, repo: @repo) + end + end + end + end +end diff --git a/lib/cocoapods-nexus/downloader.rb b/lib/cocoapods-nexus/downloader.rb new file mode 100644 index 0000000..51c90fb --- /dev/null +++ b/lib/cocoapods-nexus/downloader.rb @@ -0,0 +1,39 @@ +require 'cocoapods-downloader/remote_file' + +module Pod + module Downloader + class NexusHttp < RemoteFile + def self.options + [:type, :name] + end + + private + + executable :curl + + def download! + @filename = "#{options[:name]}.#{'podspec'.to_sym}" + @download_path = @target_path + @filename + download_file(@download_path) + end + + def download_file(full_filename) + parameters = ['-f', '-L', '-o', full_filename, url, '--create-dirs', '--netrc-optional', '--retry', '2'] + parameters << user_agent_argument if headers.nil? || + headers.none? { |header| header.casecmp(USER_AGENT_HEADER).zero? } + unless headers.nil? + headers.each do |h| + parameters << '-H' + parameters << h + end + end + # 通过curl下载文件 + curl! parameters + end + + def user_agent_argument + "-A '#{Http.user_agent_string}'" + end + end + end +end \ No newline at end of file diff --git a/lib/cocoapods-nexus/hook.rb b/lib/cocoapods-nexus/hook.rb new file mode 100644 index 0000000..f79a9c5 --- /dev/null +++ b/lib/cocoapods-nexus/hook.rb @@ -0,0 +1,2 @@ +require 'cocoapods-nexus/hook/analyzer' +require 'cocoapods-nexus/hook/manager' diff --git a/lib/cocoapods-nexus/hook/analyzer.rb b/lib/cocoapods-nexus/hook/analyzer.rb new file mode 100644 index 0000000..1df61ab --- /dev/null +++ b/lib/cocoapods-nexus/hook/analyzer.rb @@ -0,0 +1,29 @@ +# require 'cocoapods' +# require 'cocoapods-nexus/nexus_source' +# +# module Pod +# class Installer +# class Analyzer +# alias orig_sources sources +# +# # 修改pod的sources,用于注入cocoapods-nexus +# def sources +# value = orig_sources +# if podfile.sources.empty? && podfile.plugins.key?('cocoapods-nexus') +# sources = [] +# plugin_config = podfile.plugins['cocoapods-nexus'] +# # all sources declared in the plugin clause +# plugin_config['sources'].uniq.map do |config| +# name = config['name'] +# url = config['url'] +# +# sources.push(Pod::NexusSource.new("nexus_#{name}", url)) +# end +# @sources = sources +# else +# orig_sources +# end +# end +# end +# end +# end diff --git a/lib/cocoapods-nexus/hook/installer.rb b/lib/cocoapods-nexus/hook/installer.rb new file mode 100644 index 0000000..288158d --- /dev/null +++ b/lib/cocoapods-nexus/hook/installer.rb @@ -0,0 +1,49 @@ +# require 'cocoapods-core' +# require 'cocoapods-nexus/api' + +# module Pod +# class Installer +# alias_method :old_root_specs, :root_specs +# def root_specs +# # 通过修改specs的source到nexus下载zip(不完美的解决方案) +# specs = old_root_specs +# specs = specs.map{|spec| modify_spec_if_find_last_version(spec)} +# specs +# end + +# private + +# def modify_spec_if_find_last_version(spec) +# attributes_hash = spec.attributes_hash +# spec_name = attributes_hash['name'] +# spec_version = attributes_hash['version'] +# artifacte = nexus_find_artifacte(spec_name: spec_name, spec_version: spec_version) +# if artifacte != nil +# asset_zip = artifacte['assets'].select{ |asset| asset['path'].end_with?('zip') }.first +# if asset_zip != nil +# attributes_hash['source'] = { +# 'http' => asset_zip['downloadUrl'] +# } +# puts "Downloading #{spec_name}(#{spec_version})from nexus(#{asset_zip['downloadUrl']})" +# spec.attributes_hash = attributes_hash +# end +# end +# spec +# end + +# def nexus_find_artifacte(spec_name:, spec_version:) +# artifacte = {} +# api.each do |api| +# artifactes = api.search_maven_component(artifact_id: spec_name) +# artifacte = artifactes.select{ |artifacte| artifacte['version'].start_with?(spec_version) }.sort_by{ |artifacte| artifacte['version'].downcase }.last +# break if artifacte != nil +# end +# artifacte +# end + +# def api +# nexus_options = Pod::Config.instance.podfile.get_options || [] +# @apis = @apis || nexus_options.map{|nexus| CocoapodsNexus::API.new(hostname: nexus['endpoint'], repo: nexus['repo'])} +# end +# end +# end \ No newline at end of file diff --git a/lib/cocoapods-nexus/hook/manager.rb b/lib/cocoapods-nexus/hook/manager.rb new file mode 100644 index 0000000..887626e --- /dev/null +++ b/lib/cocoapods-nexus/hook/manager.rb @@ -0,0 +1,22 @@ +require 'cocoapods' +require 'cocoapods-nexus/nexus_source' + +module Pod + class Source + class Manager + alias orgin_source_from_path source_from_path + + def source_from_path(path) + @new_sources_by_path ||= Hash.new do |hash, key| + nexus_file_path = File.join(key, ".nexus") + hash[key] = if File.exist?(nexus_file_path) + Pod::NexusSource.new(key, File.read(nexus_file_path)) + else + orgin_source_from_path(key) + end + end + @new_sources_by_path[path] + end + end + end +end diff --git a/lib/cocoapods-nexus/nexus_source.rb b/lib/cocoapods-nexus/nexus_source.rb new file mode 100644 index 0000000..be5a3d1 --- /dev/null +++ b/lib/cocoapods-nexus/nexus_source.rb @@ -0,0 +1,103 @@ +require 'cocoapods-nexus/api' +require 'cocoapods-nexus/downloader' + +module Pod + class NexusSource < Source + def initialize(repo, url) + @source_url = url + super(repo) + end + + def url + if @source_url + @source_url.to_s + else + # after super(repo) repo is now the path to the repo + File.read("#{repo}/.nexus") if File.exist?("#{repo}/.nexus") + end + end + + def git? + false + end + + def type + 'nexus' + end + + # 从nexus查询依赖 + # @param [Object] query + def search(query) + unless File.exist?("#{repo}/.nexus") + raise Informative, "Unable to find a source named: `#{name}`" + end + + found = find_local_podspec(query) + # 本地没查询到,则从nexus服务查询 + if found == [] + # 暂时这样处理 + spec_version = query.requirement.requirements.last.last.to_s + artifacte = nexus_find_artifacte(spec_name: query.root_name, spec_version: spec_version) + if artifacte + download_url = parse_artifacte_asset_url(artifacte, 'podspec') + if download_url + target_path = "#{@repo}/#{query.root_name}/#{spec_version}" + downloader = Pod::Downloader::NexusHttp.new(target_path, download_url, {:type => 'podspec', :name => query.root_name}) + downloader.download + + found = find_local_podspec(query) + end + end + end + + if found == [query.root_name] + set = set(query.root_name) + set if set.specification_name == query.root_name + end + end + + def specification(name, version) + specification = super + version = version.version if version.is_a?(Pod::Version) + artifacte = nexus_find_artifacte(spec_name: name, spec_version: version) + download_url = parse_artifacte_asset_url(artifacte, 'zip') + if download_url + specification.attributes_hash['source'] = { + 'http' => download_url + } + specification.attributes_hash['vendored_frameworks'] = "#{name}.framework" + end + specification + end + + private + + # 从本地repo查询spec + def find_local_podspec(query) + query = query.root_name if query.is_a?(Dependency) + found = [] + Pathname.glob(pod_path(query)) do |path| + next unless Dir.foreach(path).any? { |child| child != '.' && child != '..' } + found << path.basename.to_s + end + found + end + + # 解析附件downloadUrl + def parse_artifacte_asset_url(artifacte, asset_type) + asset = artifacte['assets'].select { |asset| asset['path'].end_with?(asset_type) }.first + asset['downloadUrl'] unless asset['downloadUrl'].nil? + end + + def nexus_find_artifacte(spec_name:, spec_version:) + artifactes = nexus_api.search_maven_component(artifact_id: spec_name) + artifacte = artifactes.select { |artifacte| artifacte['version'].start_with?(spec_version) }.sort_by { |artifacte| artifacte['version'].downcase }.last + artifacte + end + + def nexus_api + repo_name = File.basename(@repo).gsub('nexus_', '') + @nexus_api ||= CocoapodsNexus::API.new(hostname: url, repo: repo_name) + end + end +end \ No newline at end of file diff --git a/lib/cocoapods_plugin.rb b/lib/cocoapods_plugin.rb new file mode 100644 index 0000000..83ab1b3 --- /dev/null +++ b/lib/cocoapods_plugin.rb @@ -0,0 +1,29 @@ +require 'cocoapods' +require 'cocoapods-nexus' + +Pod::HooksManager.register('cocoapods-nexus', :source_provider) do |context, options| + Pod::UI.message 'cocoapods-nexus received source_provider hook' + unless (sources = options['sources']) + raise Pod::Informative.exception 'cocoapods-nexus插件需要配置sources参数.' + end + + sources.each do |source_config| + name = source_config['name'] + url = source_config['url'] + source = Pod::NexusSource.new(File.join(Pod::Config.instance.repos_dir, name), url) + update_or_add_source(source) + context.add_source(source) + end +end + +def update_or_add_source(source) + name = source.name + url = source.url + dir = source.repo + + unless dir.exist? + argv = CLAide::ARGV.new([name, url]) + cmd = Pod::Command::Nexus::Add.new(argv) + cmd.run + end +end diff --git a/spec/command/nexus_spec.rb b/spec/command/nexus_spec.rb new file mode 100644 index 0000000..24a8ab8 --- /dev/null +++ b/spec/command/nexus_spec.rb @@ -0,0 +1,12 @@ +require File.expand_path('../../spec_helper', __FILE__) + +module Pod + describe Command::Nexus do + describe 'CLAide' do + it 'registers it self' do + Command.parse(%w{ nexus }).should.be.instance_of Command::Nexus + end + end + end +end + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..35da65d --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,50 @@ +require 'pathname' +ROOT = Pathname.new(File.expand_path('../../', __FILE__)) +$:.unshift((ROOT + 'lib').to_s) +$:.unshift((ROOT + 'spec').to_s) + +require 'bundler/setup' +require 'bacon' +require 'mocha-on-bacon' +require 'pretty_bacon' +require 'pathname' +require 'cocoapods' + +Mocha::Configuration.prevent(:stubbing_non_existent_method) + +require 'cocoapods_plugin' + +#-----------------------------------------------------------------------------# + +module Pod + + # Disable the wrapping so the output is deterministic in the tests. + # + UI.disable_wrap = true + + # Redirects the messages to an internal store. + # + module UI + @output = '' + @warnings = '' + + class << self + attr_accessor :output + attr_accessor :warnings + + def puts(message = '') + @output << "#{message}\n" + end + + def warn(message = '', actions = []) + @warnings << "#{message}\n" + end + + def print(message) + @output << message + end + end + end +end + +#-----------------------------------------------------------------------------#