xccache 0.0.1a1 → 0.0.1a3
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.
- checksums.yaml +4 -4
- data/lib/xccache/assets/templates/cachemap.html.template +55 -0
- data/lib/xccache/assets/templates/cachemap.js.template +95 -0
- data/lib/xccache/assets/templates/cachemap.style.css.template +94 -0
- data/lib/xccache/assets/templates/framework.info.plist.template +25 -0
- data/lib/xccache/assets/templates/framework.modulemap.template +6 -0
- data/lib/xccache/assets/templates/resource_bundle_accessor.m.template +27 -0
- data/lib/xccache/assets/templates/resource_bundle_accessor.swift.template +24 -0
- data/lib/xccache/assets/templates/umbrella.Package.swift.template +183 -0
- data/lib/xccache/cache/cachemap.rb +56 -0
- data/lib/xccache/command/base.rb +29 -0
- data/lib/xccache/command/build.rb +41 -0
- data/lib/xccache/command/pkg/build.rb +30 -0
- data/lib/xccache/command/pkg.rb +1 -0
- data/lib/xccache/command/remote/pull.rb +15 -0
- data/lib/xccache/command/remote/push.rb +15 -0
- data/lib/xccache/command/remote.rb +39 -0
- data/lib/xccache/command/rollback.rb +14 -0
- data/lib/xccache/command/use.rb +19 -0
- data/lib/xccache/command.rb +27 -1
- data/lib/xccache/core/cacheable.rb +28 -0
- data/lib/xccache/core/config.rb +110 -0
- data/lib/xccache/core/error.rb +7 -0
- data/lib/xccache/core/git.rb +27 -0
- data/lib/xccache/core/hash.rb +21 -0
- data/lib/xccache/core/lockfile.rb +40 -0
- data/lib/xccache/core/log.rb +51 -0
- data/lib/xccache/core/parallel.rb +10 -0
- data/lib/xccache/core/sh.rb +51 -0
- data/lib/xccache/core/syntax/hash.rb +31 -0
- data/lib/xccache/core/syntax/json.rb +16 -0
- data/lib/xccache/core/syntax/plist.rb +17 -0
- data/lib/xccache/core/syntax/yml.rb +16 -0
- data/lib/xccache/core/syntax.rb +1 -0
- data/lib/xccache/core/system.rb +62 -0
- data/lib/xccache/core.rb +1 -0
- data/lib/xccache/installer/build.rb +23 -0
- data/lib/xccache/installer/rollback.rb +37 -0
- data/lib/xccache/installer/use.rb +28 -0
- data/lib/xccache/installer.rb +113 -0
- data/lib/xccache/main.rb +3 -1
- data/lib/xccache/spm/build.rb +53 -0
- data/lib/xccache/spm/desc/base.rb +68 -0
- data/lib/xccache/spm/desc/dep.rb +40 -0
- data/lib/xccache/spm/desc/desc.rb +126 -0
- data/lib/xccache/spm/desc/product.rb +36 -0
- data/lib/xccache/spm/desc/target/binary.rb +8 -0
- data/lib/xccache/spm/desc/target/macro.rb +8 -0
- data/lib/xccache/spm/desc/target.rb +164 -0
- data/lib/xccache/spm/desc.rb +1 -0
- data/lib/xccache/spm/macro.rb +44 -0
- data/lib/xccache/spm/mixin.rb +12 -0
- data/lib/xccache/spm/pkg/base.rb +107 -0
- data/lib/xccache/spm/pkg/umbrella/build.rb +30 -0
- data/lib/xccache/spm/pkg/umbrella/cachemap.rb +110 -0
- data/lib/xccache/spm/pkg/umbrella/descs.rb +35 -0
- data/lib/xccache/spm/pkg/umbrella/manifest.rb +83 -0
- data/lib/xccache/spm/pkg/umbrella/viz.rb +40 -0
- data/lib/xccache/spm/pkg/umbrella/xcconfigs.rb +31 -0
- data/lib/xccache/spm/pkg/umbrella.rb +91 -0
- data/lib/xccache/spm/pkg.rb +1 -0
- data/lib/xccache/spm/xcframework/metadata.rb +41 -0
- data/lib/xccache/spm/xcframework/slice.rb +180 -0
- data/lib/xccache/spm/xcframework/xcframework.rb +56 -0
- data/lib/xccache/spm/xcframework.rb +2 -0
- data/lib/xccache/spm.rb +1 -0
- data/lib/xccache/storage/base.rb +26 -0
- data/lib/xccache/storage/git.rb +46 -0
- data/lib/xccache/storage/s3.rb +53 -0
- data/lib/xccache/storage.rb +1 -0
- data/lib/xccache/swift/sdk.rb +49 -0
- data/lib/xccache/swift/swiftc.rb +16 -0
- data/lib/xccache/utils/template.rb +21 -0
- data/lib/xccache/xcodeproj/build_configuration.rb +20 -0
- data/lib/xccache/xcodeproj/config.rb +9 -0
- data/lib/xccache/xcodeproj/file_system_synchronized_root_group.rb +17 -0
- data/lib/xccache/xcodeproj/group.rb +26 -0
- data/lib/xccache/xcodeproj/pkg.rb +73 -0
- data/lib/xccache/xcodeproj/pkg_product_dependency.rb +19 -0
- data/lib/xccache/xcodeproj/project.rb +83 -0
- data/lib/xccache/xcodeproj/target.rb +52 -0
- data/lib/xccache/xcodeproj.rb +2 -0
- metadata +107 -2
@@ -1,8 +1,38 @@
|
|
1
|
+
require "xccache/spm"
|
2
|
+
|
1
3
|
module XCCache
|
2
4
|
class Command
|
3
5
|
class Pkg < Command
|
4
6
|
class Build < Pkg
|
5
7
|
self.summary = "Build a Swift package into an xcframework"
|
8
|
+
def self.options
|
9
|
+
[
|
10
|
+
*Options.build_options,
|
11
|
+
["--out=foo", "Output directory for the xcframework"],
|
12
|
+
["--checksum/no-checksum", "Whether to include checksum to the binary name"],
|
13
|
+
].concat(super)
|
14
|
+
end
|
15
|
+
self.arguments = [
|
16
|
+
CLAide::Argument.new("TARGET", false, true),
|
17
|
+
]
|
18
|
+
|
19
|
+
def initialize(argv)
|
20
|
+
super
|
21
|
+
@targets = argv.arguments!
|
22
|
+
@out_dir = argv.option("out")
|
23
|
+
@include_checksum = argv.flag?("checksum")
|
24
|
+
end
|
25
|
+
|
26
|
+
def run
|
27
|
+
pkg = SPM::Package.new
|
28
|
+
pkg.build(
|
29
|
+
targets: @targets,
|
30
|
+
config: @config,
|
31
|
+
out_dir: @out_dir,
|
32
|
+
checksum: @include_checksum,
|
33
|
+
**@build_options,
|
34
|
+
)
|
35
|
+
end
|
6
36
|
end
|
7
37
|
end
|
8
38
|
end
|
data/lib/xccache/command/pkg.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
require_relative "base"
|
2
|
+
require "xccache/storage"
|
3
|
+
require "xccache/command/remote/pull"
|
4
|
+
require "xccache/command/remote/push"
|
5
|
+
|
6
|
+
module XCCache
|
7
|
+
class Command
|
8
|
+
class Remote < Command
|
9
|
+
self.abstract_command = true
|
10
|
+
self.summary = "Working with remote cache"
|
11
|
+
def self.options
|
12
|
+
[
|
13
|
+
["--branch=foo", "Cache branch (if using git) (default: main)"],
|
14
|
+
].concat(super)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(argv)
|
18
|
+
super
|
19
|
+
@branch = argv.option("branch", "main")
|
20
|
+
end
|
21
|
+
|
22
|
+
def storage
|
23
|
+
@storage ||= create_storage
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def create_storage
|
29
|
+
remote_config = config.remote_config
|
30
|
+
if (remote = remote_config["git"])
|
31
|
+
return GitStorage.new(branch: @branch, remote: remote)
|
32
|
+
elsif (s3_config = remote_config["s3"])
|
33
|
+
return S3Storage.new(uri: s3_config["uri"], creds_path: s3_config["creds"])
|
34
|
+
end
|
35
|
+
Storage.new
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "xccache/installer"
|
2
|
+
require_relative "base"
|
3
|
+
|
4
|
+
module XCCache
|
5
|
+
class Command
|
6
|
+
class Rollback < Command
|
7
|
+
self.summary = "Roll back prebuilt cache for packages"
|
8
|
+
|
9
|
+
def run
|
10
|
+
Installer::Rollback.new(ctx: self).install!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "xccache/installer"
|
2
|
+
require_relative "base"
|
3
|
+
|
4
|
+
module XCCache
|
5
|
+
class Command
|
6
|
+
class Use < Command
|
7
|
+
self.summary = "Use prebuilt cache for packages"
|
8
|
+
def self.options
|
9
|
+
[
|
10
|
+
*Options.install_options,
|
11
|
+
].concat(super)
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
Installer::Use.new(ctx: self).install!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/xccache/command.rb
CHANGED
@@ -1,10 +1,36 @@
|
|
1
1
|
require "claide"
|
2
|
+
require "xccache/core/config"
|
3
|
+
require "xccache/swift/sdk"
|
2
4
|
|
3
5
|
module XCCache
|
4
6
|
class Command < CLAide::Command
|
5
|
-
|
7
|
+
include Config::Mixin
|
8
|
+
Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
|
6
9
|
|
7
10
|
self.abstract_command = true
|
11
|
+
self.default_subcommand = "use"
|
8
12
|
self.summary = "xccache - a build caching tool"
|
13
|
+
|
14
|
+
attr_reader :install_options, :build_options
|
15
|
+
|
16
|
+
def initialize(argv)
|
17
|
+
super
|
18
|
+
config.verbose = verbose unless verbose.nil?
|
19
|
+
@install_options = {
|
20
|
+
:sdks => str_to_sdks(argv.option("sdk")),
|
21
|
+
:skip_resolving_dependencies => argv.flag?("skip-resolving-dependencies"),
|
22
|
+
}
|
23
|
+
@build_options = {
|
24
|
+
**@install_options,
|
25
|
+
:config => argv.option("config"),
|
26
|
+
:recursive => argv.flag?("recursive"),
|
27
|
+
:merge_slices => argv.flag?("merge-slices", true),
|
28
|
+
:library_evolution => argv.flag?("library-evolution"),
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def str_to_sdks(str)
|
33
|
+
(str || config.default_sdk).split(",").map { |s| Swift::Sdk.new(s) }
|
34
|
+
end
|
9
35
|
end
|
10
36
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module XCCache
|
2
|
+
module Cacheable
|
3
|
+
def cacheable(*method_names)
|
4
|
+
method_names.each do |method_name|
|
5
|
+
const_get(__cacheable_module_name).class_eval do
|
6
|
+
define_method(method_name) do |*args, **kwargs|
|
7
|
+
@_cache ||= {}
|
8
|
+
@_cache[method_name] ||= {}
|
9
|
+
@_cache[method_name][args.hash | kwargs.hash] ||=
|
10
|
+
method(method_name).super_method.call(*args, **kwargs)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def __cacheable_module_name
|
17
|
+
"#{name}_Cacheable".gsub(':', '_')
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.included(base)
|
21
|
+
base.extend(self)
|
22
|
+
|
23
|
+
module_name = base.send(:__cacheable_module_name)
|
24
|
+
remove_const(module_name) if const_defined?(module_name)
|
25
|
+
base.prepend(const_set(module_name, Module.new))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require "xcodeproj"
|
2
|
+
require "xccache/core/syntax/yml"
|
3
|
+
|
4
|
+
module XCCache
|
5
|
+
class Config < YAMLRepresentable
|
6
|
+
module Mixin
|
7
|
+
def config
|
8
|
+
Config.instance
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.instance
|
13
|
+
@instance ||= new(Pathname("xccache.yml").expand_path)
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :verbose
|
17
|
+
alias verbose? verbose
|
18
|
+
|
19
|
+
# To distinguish if it's within an installation, or standalone like `xccache pkg build`
|
20
|
+
attr_accessor :in_installation
|
21
|
+
alias in_installation? in_installation
|
22
|
+
|
23
|
+
def sandbox
|
24
|
+
@sandbox = Dir.prepare("xccache").expand_path
|
25
|
+
end
|
26
|
+
|
27
|
+
def spm_sandbox
|
28
|
+
@spm_sandbox ||= Dir.prepare(sandbox / "packages").expand_path
|
29
|
+
end
|
30
|
+
|
31
|
+
def spm_local_pkgs_dir
|
32
|
+
@spm_local_pkgs_dir ||= Dir.prepare(spm_sandbox / "local")
|
33
|
+
end
|
34
|
+
|
35
|
+
def spm_xcconfig_dir
|
36
|
+
@spm_xcconfig_dir ||= Dir.prepare(spm_sandbox / "xcconfigs")
|
37
|
+
end
|
38
|
+
|
39
|
+
def spm_repo_dir
|
40
|
+
@spm_repo_dir ||= Dir.prepare(Pathname("~/.xccache/default").expand_path)
|
41
|
+
end
|
42
|
+
|
43
|
+
def spm_binaries_dir
|
44
|
+
@spm_binaries_dir ||= Dir.prepare(spm_umbrella_sandbox / "binaries")
|
45
|
+
end
|
46
|
+
|
47
|
+
def spm_build_dir
|
48
|
+
@spm_build_dir ||= spm_umbrella_sandbox / ".build"
|
49
|
+
end
|
50
|
+
|
51
|
+
def spm_artifacts_dir
|
52
|
+
@spm_artifacts_dir ||= spm_build_dir / "artifacts"
|
53
|
+
end
|
54
|
+
|
55
|
+
def spm_umbrella_sandbox
|
56
|
+
@spm_umbrella_sandbox ||= Dir.prepare(spm_sandbox / "umbrella")
|
57
|
+
end
|
58
|
+
|
59
|
+
def spm_metadata_dir
|
60
|
+
@spm_metadata_dir ||= Dir.prepare(spm_sandbox / "metadata")
|
61
|
+
end
|
62
|
+
|
63
|
+
def lockfile
|
64
|
+
@lockfile ||= Lockfile.new(Pathname("xccache.lock").expand_path)
|
65
|
+
end
|
66
|
+
|
67
|
+
def cachemap
|
68
|
+
@cachemap ||= Cache::Cachemap.new(sandbox / "cachemap.json")
|
69
|
+
end
|
70
|
+
|
71
|
+
def projects
|
72
|
+
@projects ||= Pathname(".").glob("*.xcodeproj").map do |p|
|
73
|
+
Xcodeproj::Project.open(p)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def project_targets
|
78
|
+
projects.flat_map(&:targets)
|
79
|
+
end
|
80
|
+
|
81
|
+
def remote_config
|
82
|
+
raw["remote"] || {}
|
83
|
+
end
|
84
|
+
|
85
|
+
def ignore_list
|
86
|
+
raw["ignore"] || []
|
87
|
+
end
|
88
|
+
|
89
|
+
def ignore?(item)
|
90
|
+
return true if ignore_local? && lockfile.local_pkg_slugs.include?(item.split("/").first)
|
91
|
+
ignore_list.any? { |p| File.fnmatch(p, item) }
|
92
|
+
end
|
93
|
+
|
94
|
+
def ignore_local?
|
95
|
+
raw["ignore_local"]
|
96
|
+
end
|
97
|
+
|
98
|
+
def ignore_build_errors?
|
99
|
+
raw["ignore_build_errors"]
|
100
|
+
end
|
101
|
+
|
102
|
+
def keep_pkgs_in_project?
|
103
|
+
raw["keep_pkgs_in_project"]
|
104
|
+
end
|
105
|
+
|
106
|
+
def default_sdk
|
107
|
+
raw["default_sdk"] || "iphonesimulator"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module XCCache
|
2
|
+
class Git
|
3
|
+
attr_reader :root
|
4
|
+
|
5
|
+
def initialize(root)
|
6
|
+
@root = Pathname(root)
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(*args, **kwargs)
|
10
|
+
Sh.run("git -C #{root}", *args, **kwargs)
|
11
|
+
end
|
12
|
+
|
13
|
+
def sha
|
14
|
+
run("rev-parse --short HEAD", capture: true, log_cmd: false)[0].strip
|
15
|
+
end
|
16
|
+
|
17
|
+
def clean?
|
18
|
+
status("--porcelain", capture: true, log_cmd: false)[0].empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
%i[checkout fetch pull push clean add commit branch remote switch status].each do |name|
|
22
|
+
define_method(name) do |*args, **kwargs|
|
23
|
+
run(name, *args, **kwargs)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Hash
|
2
|
+
def deep_merge(other, uniq_block: nil, &block)
|
3
|
+
dup.deep_merge!(other, uniq_block: uniq_block, &block)
|
4
|
+
end
|
5
|
+
|
6
|
+
def deep_merge!(other, uniq_block: nil, &block)
|
7
|
+
merge!(other) do |key, this_val, other_val|
|
8
|
+
result = if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
9
|
+
this_val.deep_merge(other_val, uniq_block: uniq_block, &block)
|
10
|
+
elsif this_val.is_a?(Array) && other_val.is_a?(Array)
|
11
|
+
this_val + other_val
|
12
|
+
elsif block_given?
|
13
|
+
block.call(key, this_val, other_val)
|
14
|
+
else
|
15
|
+
other_val
|
16
|
+
end
|
17
|
+
next result if uniq_block.nil? || !result.is_a?(Array)
|
18
|
+
result.reverse.uniq(&uniq_block).reverse # prefer updates
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "xccache/core/syntax/json"
|
2
|
+
|
3
|
+
module XCCache
|
4
|
+
class Lockfile < JSONRepresentable
|
5
|
+
def hash_for_project(project)
|
6
|
+
raw[project.display_name] ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def product_dependencies_by_targets
|
10
|
+
@product_dependencies_by_targets ||= raw.values.map { |h| h["dependencies"] }.reduce { |acc, h| acc.merge(h) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def deep_merge!(hash)
|
14
|
+
keys = ["repositoryURL", "path_from_root", "relative_path"]
|
15
|
+
raw.deep_merge!(hash, uniq_block: proc do |h|
|
16
|
+
h.is_a?(Hash) && (k = keys.find { |k| h.key?(k) }) ? h[k] : h
|
17
|
+
end)
|
18
|
+
end
|
19
|
+
|
20
|
+
def pkgs
|
21
|
+
@pkgs ||= raw.values.flat_map { |h| h["packages"] || [] }
|
22
|
+
end
|
23
|
+
|
24
|
+
def local_pkgs
|
25
|
+
@local_pkgs ||= pkgs.select { |h| h.key?("relative_path") || h.key?("path") }
|
26
|
+
end
|
27
|
+
|
28
|
+
def local_pkg_slugs
|
29
|
+
@local_pkg_slugs ||= local_pkgs.map { |h| File.basename(h["relative_path"] || h["path"]) }.uniq
|
30
|
+
end
|
31
|
+
|
32
|
+
def product_dependencies
|
33
|
+
@product_dependencies ||= product_dependencies_by_targets.values.flatten.uniq
|
34
|
+
end
|
35
|
+
|
36
|
+
def targets_data
|
37
|
+
@targets_data ||= product_dependencies_by_targets.transform_keys { |k| "#{k}.xccache" }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "xccache/core/config"
|
2
|
+
require "colored2"
|
3
|
+
|
4
|
+
module XCCache
|
5
|
+
module UI
|
6
|
+
@indent = 0
|
7
|
+
|
8
|
+
class << self
|
9
|
+
include Config::Mixin
|
10
|
+
attr_accessor :indent
|
11
|
+
|
12
|
+
def section(title, timing: false)
|
13
|
+
start = Time.new if timing
|
14
|
+
UI.puts(title)
|
15
|
+
self.indent += 2
|
16
|
+
res = yield if block_given?
|
17
|
+
self.indent -= 2
|
18
|
+
if timing
|
19
|
+
duration = (Time.new - start).to_i
|
20
|
+
duration = if duration < 60 then "#{duration}s"
|
21
|
+
elsif duration < 60 * 60 then "#{duration / 60}m"
|
22
|
+
else
|
23
|
+
"#{duration / 3600}h"
|
24
|
+
end
|
25
|
+
UI.puts("-> Finished: #{title.dark} (#{duration})")
|
26
|
+
end
|
27
|
+
res
|
28
|
+
end
|
29
|
+
|
30
|
+
def message(message)
|
31
|
+
UI.puts(message) if config.verbose?
|
32
|
+
end
|
33
|
+
|
34
|
+
def info(message)
|
35
|
+
UI.puts(message)
|
36
|
+
end
|
37
|
+
|
38
|
+
def warn(message)
|
39
|
+
UI.puts(message.yellow)
|
40
|
+
end
|
41
|
+
|
42
|
+
def error(message)
|
43
|
+
UI.puts("[ERROR] #{message}".red)
|
44
|
+
end
|
45
|
+
|
46
|
+
def puts(message)
|
47
|
+
$stdout.puts("#{' ' * self.indent}#{message}")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require "parallel"
|
2
|
+
|
3
|
+
class Array
|
4
|
+
def parallel_map(options = {})
|
5
|
+
# By default, use in_threads (IO-bound tasks)
|
6
|
+
default = {}
|
7
|
+
default[:in_threads] = Parallel.processor_count unless options.key?(:in_processes)
|
8
|
+
Parallel.map(self, { **default, **options }) { |x| yield x if block_given? }
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "open3"
|
2
|
+
require "xccache/core/config"
|
3
|
+
require "xccache/core/log"
|
4
|
+
require "xccache/core/error"
|
5
|
+
|
6
|
+
module XCCache
|
7
|
+
class Sh
|
8
|
+
class ExecError < BaseError
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
include Config::Mixin
|
13
|
+
|
14
|
+
def capture_output(cmd)
|
15
|
+
run(cmd, capture: true, log_cmd: false)[0].strip
|
16
|
+
end
|
17
|
+
|
18
|
+
def run(*args, env: nil, **options)
|
19
|
+
cmd = args.join(" ")
|
20
|
+
UI.message("$ #{cmd}".cyan.dark) if config.verbose? && options[:log_cmd] != false
|
21
|
+
|
22
|
+
out, err = [], []
|
23
|
+
handle_out = proc do |line|
|
24
|
+
if options[:capture]
|
25
|
+
out << line
|
26
|
+
else
|
27
|
+
UI.puts line
|
28
|
+
end
|
29
|
+
end
|
30
|
+
handle_err = proc do |line|
|
31
|
+
if options[:capture]
|
32
|
+
err << line
|
33
|
+
else
|
34
|
+
UI.puts line.strip.yellow unless options[:suppress_err]&.match(line)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
popen3_args = env ? [env, cmd] : [cmd]
|
39
|
+
Open3.popen3(*popen3_args) do |_stdin, stdout, stderr, wait_thr|
|
40
|
+
stdout_thread = Thread.new { stdout.each { |l| handle_out.call(l) } }
|
41
|
+
stderr_thread = Thread.new { stderr.each { |l| handle_err.call(l) } }
|
42
|
+
[stdout_thread, stderr_thread].each(&:join)
|
43
|
+
result = wait_thr.value
|
44
|
+
result.exitstatus
|
45
|
+
raise ExecError, "Command '#{cmd}' failed with status: #{result.exitstatus}" unless result.success?
|
46
|
+
end
|
47
|
+
[out.join("\n"), err.join("\n")]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module XCCache
|
2
|
+
class HashRepresentable
|
3
|
+
attr_reader :path
|
4
|
+
attr_accessor :raw
|
5
|
+
|
6
|
+
def initialize(path, raw: nil)
|
7
|
+
@path = path
|
8
|
+
@raw = raw || load || {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def load
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def merge!(other)
|
16
|
+
raw.merge!(other)
|
17
|
+
end
|
18
|
+
|
19
|
+
def save(to: nil)
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](key)
|
24
|
+
raw[key]
|
25
|
+
end
|
26
|
+
|
27
|
+
def []=(key, value)
|
28
|
+
raw[key] = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "json"
|
2
|
+
require_relative "hash"
|
3
|
+
|
4
|
+
module XCCache
|
5
|
+
class JSONRepresentable < HashRepresentable
|
6
|
+
def load
|
7
|
+
JSON.parse(path.read) if path.exist?
|
8
|
+
rescue StandardError
|
9
|
+
{}
|
10
|
+
end
|
11
|
+
|
12
|
+
def save(to: nil)
|
13
|
+
(to || path).write(JSON.pretty_generate(raw))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "cfpropertylist"
|
2
|
+
require_relative "hash"
|
3
|
+
|
4
|
+
module XCCache
|
5
|
+
class PlistRepresentable < HashRepresentable
|
6
|
+
def load
|
7
|
+
plist = CFPropertyList::List.new(file: path)
|
8
|
+
CFPropertyList.native_types(plist.value)
|
9
|
+
rescue StandardError
|
10
|
+
{}
|
11
|
+
end
|
12
|
+
|
13
|
+
def save(to: nil)
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require_relative "hash"
|
3
|
+
|
4
|
+
module XCCache
|
5
|
+
class YAMLRepresentable < HashRepresentable
|
6
|
+
def load
|
7
|
+
YAML.safe_load(path.read) if path.exist?
|
8
|
+
rescue StandardError
|
9
|
+
{}
|
10
|
+
end
|
11
|
+
|
12
|
+
def save(to: nil)
|
13
|
+
(to || path).write(raw.to_yaml)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
|