carthage_remote_cache 0.0.5 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +7 -0
- data/Gemfile.lock +38 -38
- data/Guardfile +2 -2
- data/README.md +87 -5
- data/Rakefile +2 -2
- data/bin/carthagerc +24 -9
- data/carthage_remote_cache.gemspec +5 -5
- data/dev/start_server +1 -1
- data/lib/api.rb +49 -5
- data/lib/carthage_archive.rb +18 -8
- data/lib/carthage_dependency.rb +6 -27
- data/lib/carthage_remote_cache.rb +14 -13
- data/lib/commands.rb +5 -4
- data/lib/commands/download_command.rb +21 -12
- data/lib/commands/server_command.rb +2 -2
- data/lib/commands/upload_command.rb +18 -11
- data/lib/commands/verify_command.rb +18 -0
- data/lib/configuration.rb +34 -17
- data/lib/constants.rb +9 -7
- data/lib/crc32.rb +3 -3
- data/lib/errors.rb +59 -1
- data/lib/log.rb +1 -1
- data/lib/networking.rb +92 -33
- data/lib/server/config.ru +3 -3
- data/lib/server/server_app.rb +28 -9
- data/lib/shell_wrapper.rb +1 -1
- data/lib/table.rb +58 -0
- data/lib/utils.rb +20 -10
- data/lib/version.rb +1 -1
- data/lib/version_file.rb +31 -17
- metadata +27 -20
- data/com.kayak.carthagerc.server.plist +0 -20
data/lib/constants.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
CARTHAGE_DIR =
|
2
|
-
CARTHAGE_BUILD_DIR = File.join(CARTHAGE_DIR,
|
3
|
-
CARTFILE_RESOLVED =
|
4
|
-
CARTRCFILE =
|
1
|
+
CARTHAGE_DIR = "Carthage"
|
2
|
+
CARTHAGE_BUILD_DIR = File.join(CARTHAGE_DIR, "Build")
|
3
|
+
CARTFILE_RESOLVED = "Cartfile.resolved"
|
4
|
+
CARTRCFILE = "Cartrcfile"
|
5
5
|
THREAD_POOL_SIZE = 8
|
6
6
|
|
7
7
|
SERVER_DEFAULT_PORT = 9292
|
8
|
-
SERVER_CACHE_DIR = File.join(Dir.home,
|
8
|
+
SERVER_CACHE_DIR = File.join(Dir.home, ".carthagerc_server")
|
9
9
|
|
10
10
|
ARCHIVE_CHECKSUM_HEADER_REST_CLIENT = :archive_checksum
|
11
|
-
ARCHIVE_CHECKSUM_HEADER_SINATRA_IN =
|
12
|
-
ARCHIVE_CHECKSUM_HEADER_SINATRA_OUT =
|
11
|
+
ARCHIVE_CHECKSUM_HEADER_SINATRA_IN = "HTTP_ARCHIVE_CHECKSUM"
|
12
|
+
ARCHIVE_CHECKSUM_HEADER_SINATRA_OUT = "Archive-Checksum"
|
13
|
+
|
14
|
+
PLATFORMS = [:iOS, :macOS, :tvOS, :watchOS]
|
data/lib/crc32.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "digest"
|
2
|
+
require "zlib"
|
3
3
|
|
4
4
|
class Digest::CRC32 < Digest::Class
|
5
5
|
include Digest::Instance
|
@@ -17,6 +17,6 @@ class Digest::CRC32 < Digest::Class
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def finish
|
20
|
-
[@crc32].pack(
|
20
|
+
[@crc32].pack("N")
|
21
21
|
end
|
22
22
|
end
|
data/lib/errors.rb
CHANGED
@@ -19,6 +19,64 @@ class MultipleErrorsError < AppError
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
class
|
22
|
+
class VersionFileDoesNotExistError < AppError; end
|
23
|
+
|
24
|
+
class OutdatedFrameworkBuildError < AppError
|
25
|
+
attr_reader :framework_name, :build_version, :cartfile_resolved_version
|
26
|
+
|
27
|
+
def initialize(framework_name, build_version, cartfile_resolved_version)
|
28
|
+
@framework_name = framework_name
|
29
|
+
@build_version = build_version
|
30
|
+
@cartfile_resolved_version = cartfile_resolved_version
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(o)
|
34
|
+
self.class == o.class && state == o.state
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_s
|
38
|
+
"framework name: #{@framework_name}, build version: #{@build_version}, resolved version: #{@cartfile_resolved_version}"
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def state
|
44
|
+
[@framework_name, @build_version, @cartfile_resolved_version]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class FrameworkValidationError < AppError
|
49
|
+
def initialize(errors)
|
50
|
+
@errors = errors
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
header = ["Framework", CARTHAGE_BUILD_DIR, CARTFILE_RESOLVED]
|
55
|
+
rows = @errors.map { |e| [e.framework_name, e.build_version, e.cartfile_resolved_version] }
|
56
|
+
table = Table.new(header, rows)
|
57
|
+
<<~EOS
|
58
|
+
Detected differences between existing frameworks in '#{CARTHAGE_BUILD_DIR}' and entries in '#{CARTFILE_RESOLVED}':
|
59
|
+
|
60
|
+
#{table}
|
61
|
+
|
62
|
+
To resolve the issue:
|
63
|
+
- run `carthagerc download` to fetch missing frameworks from the server.
|
64
|
+
- if the issue persists, run `carthage bootstrap` to build frameworks and `carthagerc upload` to populate the server.
|
65
|
+
EOS
|
66
|
+
end
|
67
|
+
end
|
23
68
|
|
24
69
|
class MissingFrameworkDirectoryError < AppError; end
|
70
|
+
|
71
|
+
class ServerVersionMismatchError < AppError; end
|
72
|
+
|
73
|
+
class PlatformMismatchError < AppError
|
74
|
+
def initialize(platform)
|
75
|
+
@platform = platform
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_s
|
79
|
+
platforms = PLATFORMS.map(&:to_s).join(", ")
|
80
|
+
"Platform '#{@platform}' doesn't match any of: #{platforms}"
|
81
|
+
end
|
82
|
+
end
|
data/lib/log.rb
CHANGED
data/lib/networking.rb
CHANGED
@@ -1,23 +1,48 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "rest-client"
|
2
|
+
require "uri"
|
3
3
|
|
4
4
|
class Networking
|
5
|
-
def initialize(config)
|
5
|
+
def initialize(config, is_retry_enabled)
|
6
6
|
@config = config
|
7
|
+
@is_retry_enabled = is_retry_enabled
|
8
|
+
end
|
9
|
+
|
10
|
+
# Version
|
11
|
+
|
12
|
+
def get_server_version
|
13
|
+
url = new_version_url
|
14
|
+
$LOG.debug("Fetching server version from #{url}")
|
15
|
+
server_version = perform_network_request do
|
16
|
+
RestClient.get(url) do |response, request, result|
|
17
|
+
if response.code == 200
|
18
|
+
response.strip
|
19
|
+
else
|
20
|
+
raise AppError.new, "Failed to read server version from #{url}, response:\n #{response[0...300]}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
server_version
|
7
25
|
end
|
8
26
|
|
9
27
|
# Version Files
|
10
28
|
|
11
29
|
# @return VersionFile or nil
|
12
|
-
def download_version_file(carthage_dependency)
|
30
|
+
def download_version_file(carthage_dependency, platforms)
|
13
31
|
url = new_version_file_url(carthage_dependency)
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
32
|
+
params = {}
|
33
|
+
unless platforms.nil?
|
34
|
+
params[:platform] = platforms.map(&:to_s).join(",")
|
35
|
+
end
|
36
|
+
|
37
|
+
version_file = perform_network_request do
|
38
|
+
$LOG.debug("Downloading version file from #{url}, params: #{params}")
|
39
|
+
RestClient.get(url, { params: params }) do |response, request, result|
|
40
|
+
if response.code == 200
|
41
|
+
File.write(carthage_dependency.version_filename, response.to_s)
|
42
|
+
VersionFile.new(carthage_dependency.version_filename)
|
43
|
+
else
|
44
|
+
nil
|
45
|
+
end
|
21
46
|
end
|
22
47
|
end
|
23
48
|
version_file
|
@@ -26,10 +51,12 @@ class Networking
|
|
26
51
|
# @raise AppError on upload failure
|
27
52
|
def upload_version_file(carthage_dependency)
|
28
53
|
url = new_version_file_url(carthage_dependency)
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
54
|
+
perform_network_request do
|
55
|
+
$LOG.debug("Uploading #{carthage_dependency.version_filename}")
|
56
|
+
RestClient.post(url, :version_file => File.new(carthage_dependency.version_filepath)) do |response, request, result|
|
57
|
+
unless response.code == 200
|
58
|
+
raise AppError.new, "Version file upload #{carthage_dependency.version_filename} failed, response:\n #{response[0..300]}"
|
59
|
+
end
|
33
60
|
end
|
34
61
|
end
|
35
62
|
end
|
@@ -39,14 +66,16 @@ class Networking
|
|
39
66
|
# @return Hash with CarthageArchive and checksum or nil
|
40
67
|
def download_framework_archive(carthage_dependency, framework_name, platform)
|
41
68
|
url = new_framework_url(carthage_dependency, framework_name, platform)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
69
|
+
archive = perform_network_request do
|
70
|
+
$LOG.debug("Downloading framework from #{url}")
|
71
|
+
RestClient.get(url) do |response, request, result|
|
72
|
+
if response.code == 200
|
73
|
+
archive = CarthageArchive.new(framework_name, platform)
|
74
|
+
File.write(archive.archive_path, response.to_s)
|
75
|
+
{ :archive => archive, :checksum => response.headers[ARCHIVE_CHECKSUM_HEADER_REST_CLIENT] }
|
76
|
+
else
|
77
|
+
nil
|
78
|
+
end
|
50
79
|
end
|
51
80
|
end
|
52
81
|
archive
|
@@ -55,21 +84,27 @@ class Networking
|
|
55
84
|
# @raise AppError when upload fails
|
56
85
|
def upload_framework_archive(zipfile_name, carthage_dependency, framework_name, platform, checksum)
|
57
86
|
url = new_framework_url(carthage_dependency, framework_name, platform)
|
58
|
-
params = {:framework_file => File.new(zipfile_name)}
|
59
|
-
headers = {ARCHIVE_CHECKSUM_HEADER_REST_CLIENT => checksum}
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
87
|
+
params = { :framework_file => File.new(zipfile_name) }
|
88
|
+
headers = { ARCHIVE_CHECKSUM_HEADER_REST_CLIENT => checksum }
|
89
|
+
perform_network_request do
|
90
|
+
$LOG.debug("Uploading framework to #{url}, headers: #{headers}")
|
91
|
+
RestClient.post(url, params, headers) do |response, request, result|
|
92
|
+
unless response.code == 200
|
93
|
+
raise AppError.new, "Framework upload #{zipfile_name} failed, response:\n #{response[0..300]}"
|
94
|
+
end
|
64
95
|
end
|
65
96
|
end
|
66
97
|
end
|
67
98
|
|
68
99
|
private
|
69
100
|
|
101
|
+
def new_version_url
|
102
|
+
new_server_url(["version"])
|
103
|
+
end
|
104
|
+
|
70
105
|
def new_version_file_url(carthage_dependency)
|
71
106
|
new_server_url([
|
72
|
-
|
107
|
+
"versions",
|
73
108
|
@config.xcodebuild_version,
|
74
109
|
@config.swift_version,
|
75
110
|
carthage_dependency.guessed_framework_basename,
|
@@ -80,7 +115,7 @@ class Networking
|
|
80
115
|
|
81
116
|
def new_framework_url(carthage_dependency, framework_name, platform)
|
82
117
|
new_server_url([
|
83
|
-
|
118
|
+
"frameworks",
|
84
119
|
@config.xcodebuild_version,
|
85
120
|
@config.swift_version,
|
86
121
|
carthage_dependency.guessed_framework_basename,
|
@@ -96,13 +131,37 @@ class Networking
|
|
96
131
|
:scheme => @config.server_uri.scheme,
|
97
132
|
:host => @config.server_uri.host,
|
98
133
|
:port => @config.server_uri.port,
|
99
|
-
:path =>
|
134
|
+
:path => "/" + sanitized_path_slices.join("/"),
|
100
135
|
)
|
101
136
|
uri.to_s
|
102
137
|
end
|
103
138
|
|
104
139
|
# Mangle identifiers for URL paths.
|
105
140
|
def sanitized(input)
|
106
|
-
input.gsub(/\//,
|
141
|
+
input.gsub(/\//, "_")
|
142
|
+
end
|
143
|
+
|
144
|
+
def perform_network_request
|
145
|
+
if @is_retry_enabled
|
146
|
+
retries_remaining = 3
|
147
|
+
sleep_time_seconds = 5
|
148
|
+
begin
|
149
|
+
result = yield
|
150
|
+
rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
|
151
|
+
Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
|
152
|
+
if retries_remaining > 0
|
153
|
+
$LOG.warn("Network request failed - remaining retries: #{retries_remaining}, sleeping for: #{sleep_time_seconds}s, error: #{e.message}")
|
154
|
+
sleep(sleep_time_seconds)
|
155
|
+
retries_remaining -= 1
|
156
|
+
sleep_time_seconds *= 3
|
157
|
+
retry
|
158
|
+
else
|
159
|
+
raise e
|
160
|
+
end
|
161
|
+
end
|
162
|
+
result
|
163
|
+
else
|
164
|
+
yield
|
165
|
+
end
|
107
166
|
end
|
108
167
|
end
|
data/lib/server/config.ru
CHANGED
data/lib/server/server_app.rb
CHANGED
@@ -1,22 +1,41 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "json"
|
2
|
+
require "sinatra"
|
3
|
+
require "fileutils"
|
4
|
+
require "carthage_remote_cache"
|
4
5
|
|
5
|
-
get
|
6
|
+
get "/" do
|
6
7
|
"Welcome to carthage_remote_cache"
|
7
8
|
end
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
version_path = "/version"
|
11
|
+
versions_path = "/versions/:xcodebuild_version/:swift_version/:dependency_name/:version/:version_filename"
|
12
|
+
frameworks_path = "/frameworks/:xcodebuild_version/:swift_version/:dependency_name/:version/:framework_name/:platform"
|
13
|
+
|
14
|
+
get version_path do
|
15
|
+
status(200)
|
16
|
+
VERSION
|
17
|
+
end
|
11
18
|
|
12
19
|
get versions_path do
|
20
|
+
if params.key?(:platform)
|
21
|
+
begin
|
22
|
+
platforms = platform_to_symbols(params[:platform])
|
23
|
+
rescue AppError => e
|
24
|
+
status(400)
|
25
|
+
return JSON.pretty_generate({ "error" => e.message })
|
26
|
+
end
|
27
|
+
else
|
28
|
+
platforms = PLATFORMS
|
29
|
+
end
|
30
|
+
|
13
31
|
dirname = params_to_framework_dir(params)
|
14
32
|
filename = params[:version_filename]
|
15
33
|
filepath = File.join(dirname, filename)
|
16
34
|
|
17
35
|
if File.exist?(filepath)
|
18
36
|
status(200)
|
19
|
-
|
37
|
+
version_file = VersionFile.new(filepath, platforms)
|
38
|
+
JSON.pretty_generate(version_file.json)
|
20
39
|
else
|
21
40
|
status(404)
|
22
41
|
end
|
@@ -33,7 +52,7 @@ post versions_path do
|
|
33
52
|
File.delete(target_filename) if File.exist?(target_filename)
|
34
53
|
|
35
54
|
$LOG.info("Writing: #{target_filename}")
|
36
|
-
File.open(target_filename,
|
55
|
+
File.open(target_filename, "wb") do |target_file|
|
37
56
|
target_file.write(source_file.read)
|
38
57
|
end
|
39
58
|
|
@@ -69,7 +88,7 @@ post frameworks_path do
|
|
69
88
|
File.delete(target_filename) if File.exist?(target_filename)
|
70
89
|
|
71
90
|
$LOG.info("Writing: #{target_filename}")
|
72
|
-
File.open(target_filename,
|
91
|
+
File.open(target_filename, "wb") do |target_file|
|
73
92
|
target_file.write(source_file.read)
|
74
93
|
end
|
75
94
|
|
data/lib/shell_wrapper.rb
CHANGED
data/lib/table.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
class Table
|
2
|
+
def initialize(header, rows)
|
3
|
+
@header = header
|
4
|
+
@rows = rows
|
5
|
+
@column_sizes = calculate_column_sizes
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
lines = [
|
10
|
+
separator_line,
|
11
|
+
header_line,
|
12
|
+
separator_line,
|
13
|
+
@rows.map { |r| row_line(r) },
|
14
|
+
separator_line,
|
15
|
+
]
|
16
|
+
|
17
|
+
lines
|
18
|
+
.flatten
|
19
|
+
.reject { |line| line.empty? }
|
20
|
+
.join("\n")
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def calculate_column_sizes
|
26
|
+
all = [@header] + @rows
|
27
|
+
result = all.transpose.map do |row|
|
28
|
+
lengths = row.map { |r| r.length }
|
29
|
+
lengths.max + 2
|
30
|
+
end
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
def separator_line
|
35
|
+
dashes = @column_sizes.map { |size| "-" * size }
|
36
|
+
"+" + dashes.join("+") + "+"
|
37
|
+
end
|
38
|
+
|
39
|
+
def header_line
|
40
|
+
columns = @header.each_with_index.map do |column, index|
|
41
|
+
column_size = @column_sizes[index] - 1
|
42
|
+
" %-#{column_size}.#{column_size}s" % column
|
43
|
+
end
|
44
|
+
"|" + columns.join("|") + "|"
|
45
|
+
end
|
46
|
+
|
47
|
+
def row_line(row)
|
48
|
+
columns = row.each_with_index.map do |column, index|
|
49
|
+
column_size = @column_sizes[index] - 1
|
50
|
+
if index == 0
|
51
|
+
" %-#{column_size}.#{column_size}s" % column
|
52
|
+
else
|
53
|
+
"%#{column_size}.#{column_size}s " % column
|
54
|
+
end
|
55
|
+
end
|
56
|
+
"|" + columns.join("|") + "|"
|
57
|
+
end
|
58
|
+
end
|
data/lib/utils.rb
CHANGED
@@ -15,7 +15,7 @@ end
|
|
15
15
|
def quote(input)
|
16
16
|
if input.is_a? String
|
17
17
|
if input.empty?
|
18
|
-
|
18
|
+
""
|
19
19
|
else
|
20
20
|
'"' + input + '"'
|
21
21
|
end
|
@@ -23,7 +23,7 @@ def quote(input)
|
|
23
23
|
input
|
24
24
|
.map { |e| quote(e) }
|
25
25
|
.select { |e| !e.empty? }
|
26
|
-
.join(
|
26
|
+
.join(" ")
|
27
27
|
else
|
28
28
|
raise AppError.new, "Unsupported type #{input}"
|
29
29
|
end
|
@@ -32,13 +32,13 @@ end
|
|
32
32
|
def platform_to_api_string(platform)
|
33
33
|
case platform
|
34
34
|
when :iOS
|
35
|
-
|
35
|
+
"iOS"
|
36
36
|
when :macOS
|
37
|
-
|
37
|
+
"macOS"
|
38
38
|
when :tvOS
|
39
|
-
|
39
|
+
"tvOS"
|
40
40
|
when :watchOS
|
41
|
-
|
41
|
+
"watchOS"
|
42
42
|
else
|
43
43
|
raise AppError.new, "Unrecognized platform #{platform.inspect}"
|
44
44
|
end
|
@@ -47,18 +47,28 @@ end
|
|
47
47
|
def platform_to_carthage_dir_string(platform)
|
48
48
|
case platform
|
49
49
|
when :iOS
|
50
|
-
|
50
|
+
"iOS"
|
51
51
|
when :macOS
|
52
|
-
|
52
|
+
"Mac"
|
53
53
|
when :tvOS
|
54
|
-
|
54
|
+
"tvOS"
|
55
55
|
when :watchOS
|
56
|
-
|
56
|
+
"watchOS"
|
57
57
|
else
|
58
58
|
raise AppError.new, "Unrecognized platform #{platform.inspect}"
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
+
def platform_to_symbols(string)
|
63
|
+
platforms = string.split(",").map(&:to_sym)
|
64
|
+
for platform in platforms
|
65
|
+
if !PLATFORMS.include?(platform)
|
66
|
+
raise PlatformMismatchError.new(platform)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
platforms
|
70
|
+
end
|
71
|
+
|
62
72
|
# @return string in "x.y MB" format
|
63
73
|
def format_file_size(bytes)
|
64
74
|
megabytes = bytes / 1000.0 / 1000.0
|