carthage_remote_cache 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ class CarthageDependency
2
+
3
+ class << self
4
+ # Parses Cartfile.resolved dependency entry, e.g.
5
+ # github "CocoaLumberjack/CocoaLumberjack" "3.2.1"
6
+ def parse_cartfile_resolved_line(line)
7
+ line.strip!
8
+ matches = line.match(/^(\w+)\s+\"([^\"]+)\"(\s+\"([^\"]+)\")$/)
9
+ return nil if matches.nil?
10
+ if matches.length == 5
11
+ CarthageDependency.new(type: matches[1], repository: matches[2], version: matches[4])
12
+ else
13
+ nil
14
+ end
15
+ end
16
+ end
17
+
18
+ attr_reader :type, :repository, :version
19
+
20
+ def initialize(args)
21
+ @type = args[:type]
22
+ @repository = args[:repository]
23
+ @version = args[:version]
24
+ end
25
+
26
+ # Since one Cartfile.resolved entry may produce multiple differently named frameworks,
27
+ # this is an entry point to identifying a framework name.
28
+ def guessed_framework_basename
29
+ case @type
30
+ when "github"
31
+ repository.split("/").last
32
+ else
33
+ raise "Determining version_filename from #{@type} dependency is not yet supported"
34
+ end
35
+ end
36
+
37
+ def version_filename
38
+ ".#{guessed_framework_basename}.version"
39
+ end
40
+
41
+ def version_filepath
42
+ File.join(CARTHAGE_BUILD_DIR, version_filename)
43
+ end
44
+
45
+ def validate_version_file(version_file)
46
+ raise OutdatedFrameworkBuildError.new, version_validation_message(version_file) if @version != version_file.version
47
+ end
48
+
49
+ def to_s
50
+ "#{@type} \"#{@repository}\" \"#{@version}\""
51
+ end
52
+
53
+ private
54
+
55
+ def version_validation_message(version_file)
56
+ <<~EOS
57
+ Outdated version of '#{guessed_framework_basename}' framework detected:
58
+ Expected version '#{@version}'
59
+ Found version '#{version_file.version}'
60
+
61
+ Please run `carthage bootstrap` to build frameworks.
62
+ EOS
63
+ end
64
+
65
+ end
@@ -0,0 +1,13 @@
1
+ lib = File.expand_path("..", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'api'
5
+ require 'carthage_archive'
6
+ require 'carthage_dependency'
7
+ require 'constants'
8
+ require 'configuration'
9
+ require 'errors'
10
+ require 'log'
11
+ require 'networking'
12
+ require 'utils'
13
+ require 'version_file'
@@ -0,0 +1,67 @@
1
+ require 'concurrent'
2
+
3
+ class DownloadCommand
4
+
5
+ def initialize(options)
6
+ @options = options
7
+ @config = Configuration.new
8
+ @networking = Networking.new(@config)
9
+ @api = API.new(@networking, options)
10
+ end
11
+
12
+ def run
13
+ pool = Concurrent::FixedThreadPool.new(THREAD_POOL_SIZE)
14
+
15
+ @number_of_downloaded_archives = 0
16
+ @number_of_skipped_archives = 0
17
+ errors = Concurrent::Array.new
18
+
19
+ for carthage_dependency in @config.carthage_dependencies
20
+ pool.post(carthage_dependency) do |carthage_dependency|
21
+ begin
22
+ download(carthage_dependency)
23
+ rescue => e
24
+ errors << e
25
+ end
26
+ end
27
+ end
28
+
29
+ pool.shutdown
30
+ pool.wait_for_termination
31
+
32
+ if errors.count > 0
33
+ raise MultipleErrorsError.new(errors)
34
+ else
35
+ puts "Downloaded and extracted #{@number_of_downloaded_archives} archives, skipped #{@number_of_skipped_archives} archives."
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def download(carthage_dependency)
42
+ local_version_file = if File.exist?(carthage_dependency.version_filepath)
43
+ VersionFile.new(carthage_dependency.version_filepath)
44
+ else
45
+ nil
46
+ end
47
+
48
+ if !local_version_file.nil? && @api.version_file_matches_server?(carthage_dependency, local_version_file)
49
+ $LOG.debug("Version file #{local_version_file.path} matches server version, skipping download")
50
+ @number_of_skipped_archives += local_version_file.number_of_frameworks
51
+ return
52
+ end
53
+
54
+ version_file = @networking.download_version_file(carthage_dependency)
55
+ raise "Version file #{carthage_dependency.version_filename} is not present on the server, please run `carthagerc upload` first" if version_file.nil?
56
+
57
+ version_file.frameworks_by_platform.each do |platform, framework_names|
58
+ for framework_name in framework_names do
59
+ archive = @api.download_and_unpack_archive(carthage_dependency, framework_name, platform)
60
+ raise "Failed to download framework #{carthage_dependency} – #{framework_name} (#{platform}). Please `upload` the framework first." if archive.nil?
61
+ @number_of_downloaded_archives += 1
62
+ end
63
+ end
64
+ version_file.move_to_build_dir
65
+ end
66
+
67
+ end
@@ -0,0 +1,26 @@
1
+ class InitCommand
2
+
3
+ def initialize(options)
4
+ @options = options
5
+ end
6
+
7
+ def run
8
+ path = File.join(Dir.pwd, CARTRCFILE)
9
+ if File.exist?(path)
10
+ raise "File #{path} already exists"
11
+ else
12
+ File.write(path, file_contents)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def file_contents
19
+ <<~EOS
20
+ Configuration.setup do |c|
21
+ c.server = "http://localhost:#{SERVER_DEFAULT_PORT}/"
22
+ end
23
+ EOS
24
+ end
25
+
26
+ end
@@ -0,0 +1,16 @@
1
+ class ServerCommand
2
+
3
+ def initialize(options)
4
+ @options = options
5
+ end
6
+
7
+ def run
8
+ ENV['RACK_ENV'] = 'production'
9
+ require 'server/server_app'
10
+ Rack::Handler::WEBrick.run(
11
+ Sinatra::Application,
12
+ :Port => @options[:server_port]
13
+ )
14
+ end
15
+
16
+ end
@@ -0,0 +1,63 @@
1
+ require 'concurrent'
2
+
3
+ class UploadCommand
4
+
5
+ def initialize(options)
6
+ @config = Configuration.new
7
+ @networking = Networking.new(@config)
8
+ @api = API.new(@networking, options)
9
+ end
10
+
11
+ def run
12
+ pool = Concurrent::FixedThreadPool.new(THREAD_POOL_SIZE)
13
+
14
+ $LOG.debug("Will upload frameworks: #{@config.all_framework_names}")
15
+
16
+ @number_of_uploaded_archives = 0
17
+ @number_of_skipped_archives = 0
18
+ errors = Concurrent::Array.new
19
+
20
+ for carthage_dependency in @config.carthage_dependencies
21
+ pool.post(carthage_dependency) do |carthage_dependency|
22
+ begin
23
+ upload(carthage_dependency)
24
+ rescue => e
25
+ errors << e
26
+ end
27
+ end
28
+ end
29
+
30
+ pool.shutdown
31
+ pool.wait_for_termination
32
+
33
+ if errors.count > 0
34
+ raise MultipleErrorsError.new(errors)
35
+ else
36
+ puts "Uploaded #{@number_of_uploaded_archives} archives, skipped #{@number_of_skipped_archives}."
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def upload(carthage_dependency)
43
+ version_file = VersionFile.new(carthage_dependency.version_filepath)
44
+
45
+ carthage_dependency.validate_version_file(version_file)
46
+
47
+ if @api.version_file_matches_server?(carthage_dependency, version_file)
48
+ $LOG.debug("Version file #{version_file.path} matches server version, skipping upload")
49
+ @number_of_skipped_archives += version_file.number_of_frameworks
50
+ return
51
+ end
52
+
53
+ @networking.upload_version_file(carthage_dependency)
54
+
55
+ version_file.frameworks_by_platform.each do |platform, framework_names|
56
+ for framework_name in framework_names
57
+ @api.create_and_upload_archive(carthage_dependency, framework_name, platform)
58
+ @number_of_uploaded_archives += 1
59
+ end
60
+ end
61
+ end
62
+
63
+ end
data/lib/commands.rb ADDED
@@ -0,0 +1,7 @@
1
+ lib = File.expand_path("..", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'commands/download_command'
5
+ require 'commands/init_command'
6
+ require 'commands/server_command'
7
+ require 'commands/upload_command'
@@ -0,0 +1,81 @@
1
+ require 'uri'
2
+
3
+ class Configuration
4
+
5
+ class UserConfig
6
+ attr_accessor :server
7
+ end
8
+
9
+ @@user_config = UserConfig.new
10
+
11
+ def self.setup
12
+ yield(@@user_config)
13
+ end
14
+
15
+ attr_reader :xcodebuild_version, :swift_version, :carthage_dependencies, :server_uri
16
+
17
+ def initialize
18
+ initialize_env
19
+ initialize_cartrcfile
20
+ end
21
+
22
+ def all_framework_names
23
+ version_files.flat_map { |vf| vf.framework_names }.uniq.sort
24
+ end
25
+
26
+ def to_s
27
+ <<~EOS
28
+ Xcodebuild: #{@xcodebuild_version}
29
+ ---
30
+ Swift: #{@swift_version}
31
+ ---
32
+ Server: #{@server_uri.to_s}
33
+ ---
34
+ Cartfile.resolved:
35
+ #{@carthage_dependencies.join("\n")}
36
+ ---
37
+ Local Build Frameworks:
38
+ #{framework_names_with_platforms.join("\n")}
39
+ EOS
40
+ end
41
+
42
+ private
43
+
44
+ def initialize_env
45
+ xcodebuild_raw_version = sh("xcodebuild -version")
46
+ @xcodebuild_version = xcodebuild_raw_version[/Build version (.*)$/, 1]
47
+ raise "Could not parse build version from '#{xcodebuild_raw_version}'" if @xcodebuild_version.nil?
48
+
49
+ swift_raw_version = sh("swift -version")
50
+ @swift_version = swift_raw_version[/Apple Swift version (.*) \(/, 1]
51
+ raise "Could not parse swift version from '#{raw_swift_version}'" if @swift_version.nil?
52
+
53
+ raise "Misssing #{CARTFILE_RESOLVED}" unless File.exist?(CARTFILE_RESOLVED)
54
+ @carthage_dependencies = File.readlines(CARTFILE_RESOLVED)
55
+ .map { |line| CarthageDependency.parse_cartfile_resolved_line(line) }
56
+ .compact
57
+ end
58
+
59
+ def initialize_cartrcfile
60
+ raise "Configuration file #{CARTRCFILE} was not found, consider creating one by running `carthagerc init`" unless File.exist?(CARTRCFILE)
61
+
62
+ # Populate class variable @@user_config.
63
+ load File.join(Dir.pwd, CARTRCFILE)
64
+
65
+ raise "Missing 'server' configuration in #{CARTRCFILE}" if @@user_config.server.nil? || @@user_config.server.empty?
66
+ @server_uri = URI.parse(@@user_config.server)
67
+ end
68
+
69
+ def framework_names_with_platforms
70
+ version_files.flat_map do |vf|
71
+ vf.platforms_by_framework.flat_map do |framework_name, platforms|
72
+ "#{framework_name} #{vf.version} #{platforms}"
73
+ end
74
+ end
75
+ end
76
+
77
+ def version_files
78
+ @carthage_dependencies.map { |d| VersionFile.new(d.version_filepath) }
79
+ end
80
+
81
+ end
data/lib/constants.rb ADDED
@@ -0,0 +1,8 @@
1
+ CARTHAGE_DIR = 'Carthage'
2
+ CARTHAGE_BUILD_DIR = File.join(CARTHAGE_DIR, 'Build')
3
+ CARTFILE_RESOLVED = 'Cartfile.resolved'
4
+ CARTRCFILE = 'Cartrcfile'
5
+ THREAD_POOL_SIZE = 8
6
+
7
+ SERVER_DEFAULT_PORT = 9292
8
+ SERVER_CACHE_DIR = File.join(Dir.home, '.carthage-remote-cache')
data/lib/errors.rb ADDED
@@ -0,0 +1,13 @@
1
+ class MultipleErrorsError < StandardError
2
+
3
+ def initialize(errors)
4
+ @errors = errors
5
+ end
6
+
7
+ def message
8
+ @errors.map { |e| e.message }.join("\n")
9
+ end
10
+
11
+ end
12
+
13
+ class OutdatedFrameworkBuildError < StandardError; end
data/lib/log.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'logger'
2
+
3
+ $LOG = Logger.new(STDOUT)
4
+ $LOG.level = Logger::INFO
data/lib/networking.rb ADDED
@@ -0,0 +1,96 @@
1
+ require 'rest-client'
2
+ require 'uri'
3
+
4
+ class Networking
5
+
6
+ def initialize(config)
7
+ @config = config
8
+ end
9
+
10
+ # Version Files
11
+
12
+ def download_version_file(carthage_dependency)
13
+ url = new_version_file_url(carthage_dependency)
14
+ $LOG.debug("Downloading version file from #{url}")
15
+ version_file = RestClient.get(url) do |response, request, result|
16
+ if response.code == 200
17
+ File.write(carthage_dependency.version_filename, response.to_s)
18
+ VersionFile.new(carthage_dependency.version_filename)
19
+ else
20
+ nil
21
+ end
22
+ end
23
+ version_file
24
+ end
25
+
26
+ def upload_version_file(carthage_dependency)
27
+ url = new_version_file_url(carthage_dependency)
28
+ $LOG.debug("Uploading #{carthage_dependency.version_filename}")
29
+ RestClient.post(url, :version_file => File.new(carthage_dependency.version_filepath))
30
+ end
31
+
32
+ # Archives
33
+
34
+ def download_framework_archive(carthage_dependency, framework_name, platform)
35
+ url = new_framework_url(carthage_dependency, framework_name, platform)
36
+ $LOG.debug("Downloading framework from #{url}")
37
+ archive = RestClient.get(url) do |response, request, result|
38
+ if response.code == 200
39
+ archive = CarthageArchive.new(framework_name, platform)
40
+ File.write(archive.archive_path, response.to_s)
41
+ archive
42
+ else
43
+ nil
44
+ end
45
+ end
46
+ archive
47
+ end
48
+
49
+ def upload_framework_archive(zipfile_name, carthage_dependency, framework_name, platform)
50
+ url = new_framework_url(carthage_dependency, framework_name, platform)
51
+ $LOG.debug("Uploading framework to #{url}")
52
+ RestClient.post(url, :framework_file => File.new(zipfile_name))
53
+ end
54
+
55
+ private
56
+
57
+ def new_version_file_url(carthage_dependency)
58
+ new_server_url([
59
+ 'versions',
60
+ @config.xcodebuild_version,
61
+ @config.swift_version,
62
+ carthage_dependency.guessed_framework_basename,
63
+ carthage_dependency.version,
64
+ carthage_dependency.version_filename,
65
+ ])
66
+ end
67
+
68
+ def new_framework_url(carthage_dependency, framework_name, platform)
69
+ new_server_url([
70
+ 'frameworks',
71
+ @config.xcodebuild_version,
72
+ @config.swift_version,
73
+ carthage_dependency.guessed_framework_basename,
74
+ carthage_dependency.version,
75
+ framework_name,
76
+ platform_to_api_string(platform),
77
+ ])
78
+ end
79
+
80
+ def new_server_url(path_slices)
81
+ sanitized_path_slices = path_slices.map { |p| sanitized(p) }
82
+ uri = URI::HTTP.build(
83
+ :scheme => @config.server_uri.scheme,
84
+ :host => @config.server_uri.host,
85
+ :port => @config.server_uri.port,
86
+ :path => '/' + sanitized_path_slices.join('/')
87
+ )
88
+ uri.to_s
89
+ end
90
+
91
+ # Mangle identifiers for URL paths.
92
+ def sanitized(input)
93
+ input.gsub(/\//, '_')
94
+ end
95
+
96
+ end
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.require
4
+
5
+ require './server_app.rb'
6
+ run Sinatra::Application
@@ -0,0 +1,93 @@
1
+ require 'sinatra'
2
+ require 'fileutils'
3
+ require 'carthage_remote_cache'
4
+
5
+ get '/' do
6
+ "Welcome to carthage_remote_cache"
7
+ end
8
+
9
+ versions_path = '/versions/:xcodebuild_version/:swift_version/:dependency_name/:version/:version_filename'
10
+ frameworks_path = '/frameworks/:xcodebuild_version/:swift_version/:dependency_name/:version/:framework_name/:platform'
11
+
12
+ get versions_path do
13
+ dirname = params_to_framework_dir(params)
14
+ filename = params[:version_filename]
15
+ filepath = File.join(dirname, filename)
16
+
17
+ if File.exist?(filepath)
18
+ status(200)
19
+ send_file(filepath)
20
+ else
21
+ status(404)
22
+ end
23
+ end
24
+
25
+ post versions_path do
26
+ dirname = params_to_framework_dir(params)
27
+ filename = params[:version_filename]
28
+
29
+ source_file = params[:version_file][:tempfile]
30
+ target_filename = File.join(dirname, filename)
31
+
32
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
33
+ File.delete(target_filename) if File.exist?(target_filename)
34
+
35
+ $LOG.info("Writing #{target_filename}")
36
+ File.open(target_filename, 'wb') do |target_file|
37
+ target_file.write(source_file.read)
38
+ end
39
+
40
+ status(200)
41
+ end
42
+
43
+ # Check whether framework archive is already cached.
44
+ head frameworks_path do
45
+ dirname = params_to_framework_dir(params)
46
+ archive = CarthageArchive.new(params[:framework_name], params[:platform].to_sym)
47
+ filename = File.join(dirname, archive.archive_filename)
48
+
49
+ if File.exist?(filename)
50
+ status(200)
51
+ else
52
+ status(404)
53
+ end
54
+ end
55
+
56
+ # Retrieve .zip framework archive.
57
+ get frameworks_path do
58
+ dirname = params_to_framework_dir(params)
59
+ archive = CarthageArchive.new(params[:framework_name], params[:platform].to_sym)
60
+ filename = File.join(dirname, archive.archive_filename)
61
+
62
+ if File.exist?(filename)
63
+ status(200)
64
+ send_file(filename)
65
+ else
66
+ status(404)
67
+ end
68
+ end
69
+
70
+ # Upload framework archive. Overwrites already cached archive if exists.
71
+ post frameworks_path do
72
+ filename = params[:framework_file][:filename]
73
+ source_file = params[:framework_file][:tempfile]
74
+
75
+ dirname = params_to_framework_dir(params)
76
+ target_filename = File.join(dirname, filename)
77
+
78
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
79
+ File.delete(target_filename) if File.exist?(target_filename)
80
+
81
+ $LOG.info("Writing: #{target_filename}")
82
+ File.open(target_filename, 'wb') do |target_file|
83
+ target_file.write(source_file.read)
84
+ end
85
+
86
+ status(200)
87
+ end
88
+
89
+ private
90
+
91
+ def params_to_framework_dir(params)
92
+ File.join(SERVER_CACHE_DIR, params[:xcodebuild_version], params[:swift_version], params[:dependency_name], params[:version])
93
+ end
data/lib/utils.rb ADDED
@@ -0,0 +1,62 @@
1
+ def sh(cmd)
2
+ output = `#{cmd}`
3
+ bail("Command `#{cmd}` failed!") unless $?.success?
4
+ output.strip
5
+ end
6
+
7
+ # Exits Ruby process, only to be called:
8
+ # 1. If sh / system calls fail
9
+ # 2. From top level `carthagerc` script
10
+ def bail(message, code = 1)
11
+ $stderr.puts(message.strip + "\n")
12
+ Process.exit(code)
13
+ end
14
+
15
+ # Quote command line arguments with double quotes.
16
+ # Useful for file paths with spaces.
17
+ def quote(input)
18
+ if input.is_a? String
19
+ if input.empty?
20
+ ''
21
+ else
22
+ '"' + input + '"'
23
+ end
24
+ elsif input.is_a? Array
25
+ input
26
+ .map { |e| quote(e) }
27
+ .select { |e| !e.empty? }
28
+ .join(' ')
29
+ else
30
+ raise "Unsupported type #{input}"
31
+ end
32
+ end
33
+
34
+ def platform_to_api_string(platform)
35
+ case platform
36
+ when :iOS
37
+ 'iOS'
38
+ when :macOS
39
+ 'macOS'
40
+ when :tvOS
41
+ 'tvOS'
42
+ when :watchOS
43
+ 'watchOS'
44
+ else
45
+ raise "Unrecognized platform #{platform.inspect}"
46
+ end
47
+ end
48
+
49
+ def platform_to_carthage_dir_string(platform)
50
+ case platform
51
+ when :iOS
52
+ 'iOS'
53
+ when :macOS
54
+ 'Mac'
55
+ when :tvOS
56
+ 'tvOS'
57
+ when :watchOS
58
+ 'watchOS'
59
+ else
60
+ raise "Unrecognized platform #{platform.inspect}"
61
+ end
62
+ end