├── .gitignore ├── LICENSE ├── README.md ├── Rakefile ├── lib └── app.rb ├── main.rb └── test ├── test_helper.rb └── test_main.rb /.gitignore: -------------------------------------------------------------------------------- 1 | /Carthage 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 unchartedworks 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Concurrent Carthage 2 | 3 | **Concurrent** Carthage is a faster Carthage which utilizes multiple cores to build frameworks. 4 | 5 | ## Install 6 | ``` ruby main.rb ``` 7 | 8 | 9 | ## Speed Comparasion 10 | Concurrent Carthage 11 | ``` 12 | carthage build --platform iOS --platform macOS 231.98s user 100.72s system 455% cpu 1:13.01 total 13 | ``` 14 | 15 | Original Carthage 16 | ``` 17 | carthage build --platform iOS --platform macOS 125.28s user 54.45s system 86% cpu 3:26.99 total 18 | ``` 19 | 20 | ## Limitations 21 | If your frameworks have too many nested dependencies, Concurrent Carthage can't speed it up very much. 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rake/testtask' 4 | 5 | desc 'Build' 6 | task :build do 7 | ruby 'main.rb' 8 | end 9 | 10 | Rake::TestTask.new(:test) do |t| 11 | t.libs << 'test' 12 | t.libs << 'lib' 13 | t.test_files = FileList['test/**/test_*.rb'] 14 | end 15 | 16 | task default: :build 17 | -------------------------------------------------------------------------------- /lib/app.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | require 'open3' 5 | 6 | # rubocop:disable Metrics/ClassLength 7 | # App 8 | class App 9 | # xcode-select 10 | def check 11 | puts 'Check' 12 | cmd = 'xcode-select --version' 13 | stdout, _stderr, status = Open3.capture3(cmd) 14 | 15 | # rubocop:disable Layout/LineLength 16 | raise "xcode-select is not installed. Please execute the command below to install it.\nxcode-select install" unless stdout.include? 'xcode-select version ' 17 | 18 | # rubocop:enable Layout/LineLength 19 | # rubocop:disable Style/NumericPredicate 20 | status == 0 21 | # rubocop:enable Style/NumericPredicate 22 | end 23 | 24 | # clone 25 | def clone 26 | url = 'https://github.com/Carthage/Carthage.git' 27 | puts 'Clone ' + url + ' to ../Carthage' 28 | Dir.chdir parent_path 29 | cmd = 'git clone ' + url 30 | _stdout, stderr, status = Open3.capture3(cmd) 31 | 32 | raise 'Failed to clone.' unless is_cloned?(status) || was_cloned?(stderr) 33 | 34 | true 35 | end 36 | 37 | # rubocop:disable Style/NumericPredicate 38 | # rubocop:disable Naming/PredicateName 39 | def is_cloned?(status) 40 | status == 0 41 | end 42 | # rubocop:enable Naming/PredicateName 43 | 44 | def was_cloned?(stderr) 45 | stderr.include?(carthage_exists_error) && File.file?(project_file_path) 46 | end 47 | 48 | # rubocop:disable Layout/LineLength 49 | def carthage_exists_error 50 | "fatal: destination path 'Carthage' already exists and is not an empty directory." 51 | end 52 | # rubocop:enable Layout/LineLength 53 | private :is_cloned?, :was_cloned?, :carthage_exists_error 54 | 55 | # checkout 56 | def checkout 57 | puts 'Checkout' 58 | Dir.chdir carthage_path 59 | cmd = 'git checkout tags/' + carthage_tag 60 | _stdout, _stderr, status = Open3.capture3(cmd) 61 | is_checkouted = status == 0 62 | 63 | raise 'Failed to checkout tags/' + carthage_tag unless is_checkouted 64 | end 65 | # rubocop:enable Style/NumericPredicate 66 | 67 | # patch 68 | def patch 69 | puts 'Patch' 70 | Dir.chdir carthage_path 71 | path = project_file_path 72 | code = File.read(path) 73 | result = replacements.inject(code) { |r, xs| r.gsub(xs[0], xs[1]) } 74 | result += flatten_strategy_extension 75 | File.write(path, result) 76 | end 77 | 78 | def parent_path 79 | Pathname(__dir__).parent.to_s 80 | end 81 | 82 | def carthage_path 83 | parent_path + '/Carthage/' 84 | end 85 | 86 | def project_file_path 87 | carthage_path + 'Source/CarthageKit/Project.swift' 88 | end 89 | 90 | # rubocop:disable Layout/LineLength 91 | def source 92 | " .flatMap(.concat) { dependency, version -> SignalProducer<((Dependency, PinnedVersion), Set, Bool?), CarthageError> in 93 | .flatMap(.concat) { dependency, version -> BuildSchemeProducer in 94 | let dependencyPath = self.directoryURL.appendingPathComponent(dependency.relativePath, isDirectory: true).path 95 | if !FileManager.default.fileExists(atPath: dependencyPath) { 96 | return .empty 97 | } 98 | " 99 | end 100 | # rubocop:enable Layout/LineLength 101 | 102 | def flatten_strategy_extension 103 | "extension FlattenStrategy { 104 | static let maxConcurrent: FlattenStrategy = { 105 | let n = UInt(ProcessInfo().processorCount * 2) 106 | return FlattenStrategy.concurrent(limit: n) 107 | }() 108 | } 109 | " 110 | end 111 | 112 | def replacements 113 | [ 114 | ['.flatMap(.concat) { dependency, version -> BuildSchemeProducer', 115 | '.flatMap(.maxConcurrent) { dependency, version -> BuildSchemeProducer'], 116 | ['.flatMap(.concat) { dependency, version -> SignalProducer', 117 | '.flatMap(.maxConcurrent) { dependency, version -> SignalProducer'], 118 | [flatten_strategy_extension, ''] 119 | ] 120 | end 121 | 122 | def carthage_tag 123 | `git tag --sort=committerdate | tail -1` 124 | end 125 | 126 | private :source, :flatten_strategy_extension, :replacements 127 | 128 | # build 129 | def build 130 | puts 'Build' 131 | Dir.chdir carthage_path 132 | cmd = 'make installables' 133 | success = system cmd 134 | raise 'Failed to build.' unless success 135 | 136 | true 137 | end 138 | 139 | # install 140 | def install 141 | puts 'Install' 142 | Dir.chdir carthage_path 143 | cmd = 'make install' 144 | success = system cmd 145 | raise 'Failed to install.' unless success 146 | end 147 | 148 | # main 149 | def run 150 | check 151 | clone 152 | checkout 153 | patch 154 | build 155 | install 156 | rescue StandardError => e 157 | puts e 158 | end 159 | end 160 | # rubocop:enable Metrics/ClassLength 161 | -------------------------------------------------------------------------------- /main.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # main 4 | require_relative 'lib/app' 5 | 6 | app = App.new 7 | app.run 8 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.unshift File.expand_path('../lib', __dir__) 4 | require 'app' 5 | require 'minitest/autorun' 6 | -------------------------------------------------------------------------------- /test/test_main.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class TestApp < Minitest::Test 4 | def setup 5 | @app = App.new 6 | end 7 | 8 | def test_check 9 | assert_equal true, @app.check 10 | end 11 | 12 | def test_clone 13 | assert_equal true, @app.clone 14 | end 15 | 16 | def test_build 17 | assert_equal true, @app.build 18 | end 19 | end 20 | --------------------------------------------------------------------------------