xccache 1.0.3 → 1.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec431281d554462ef40e31a982b9198f5e0b3e8201fe26d97b29dc776595c9e6
4
- data.tar.gz: 265f10b7ab38e2d2351e6b5b0e5f337bacbe64499263f4ce736131691646dc83
3
+ metadata.gz: e985d50b212e526c36c94d40d93210a81850267fb205a3c2b10aee659357a039
4
+ data.tar.gz: 8e720a6bda28fc18cefd3bcc18b37aab2ecbf29acee27f688db5508ab9c38af2
5
5
  SHA512:
6
- metadata.gz: 50a9eaba6ffeb480fd36688171fb9c97852a500f466b6b95d700dda1a134129b784bc916799a970ec9262f64e74930f497c72adfe95c1ec78f31b5ca9bbf9e9c
7
- data.tar.gz: 45b141c6b9136dfeacce7129956c2147f8e5a20e2427206509c144ed1ca543b79c7b36c81cd1f6369a97ee639d7ad024030382fc285d261b5fe0a691fdbe5203
6
+ metadata.gz: 7f84d4d129ae5e4b5e07aa96dd47d9618d9f117f2a28679f49de286075173eac12c51e8c4c47b52d85da20d5fb024cd395b1e76c3203598a5199a05c278d6027
7
+ data.tar.gz: e0d610301f133cc22ede54d34db62e57bf1f98701b3f57b408217c6a92ccb21ba6a65a0296e102c98bd337963544d4333918aa9c4aa6556c1eea118343c7ef69
@@ -5,6 +5,7 @@ module XCCache
5
5
  class Options
6
6
  SDK = ["--sdk=foo,bar", "SDKs (iphonesimulator, iphoneos, macos, etc.)"].freeze
7
7
  CONFIG = ["--config=foo", "Configuration (debug, release) (default: debug)"].freeze
8
+ LOG_DIR = ["--log-dir=foo", "Build log directory"].freeze
8
9
  MERGE_SLICES = [
9
10
  "--merge-slices/--no-merge-slices",
10
11
  "Whether to merge with existing slices/sdks in the xcframework (default: true)",
@@ -19,7 +20,7 @@ module XCCache
19
20
  end
20
21
 
21
22
  def self.build_options
22
- install_options + [MERGE_SLICES, LIBRARY_EVOLUTION]
23
+ install_options + [LOG_DIR, MERGE_SLICES, LIBRARY_EVOLUTION]
23
24
  end
24
25
  end
25
26
  end
@@ -24,6 +24,7 @@ module XCCache
24
24
  }
25
25
  @build_options = {
26
26
  **@install_options,
27
+ :log_dir => argv.option("log-dir"),
27
28
  :recursive => argv.flag?("recursive"),
28
29
  :merge_slices => argv.flag?("merge-slices", true),
29
30
  :library_evolution => argv.flag?("library-evolution"),
@@ -0,0 +1,82 @@
1
+ require "monitor"
2
+ require "tty-cursor"
3
+ require "tty-screen"
4
+
5
+ module XCCache
6
+ class LiveLog
7
+ include UI::Mixin
8
+ CURSOR_LOCK = Monitor.new
9
+
10
+ attr_reader :output, :max_lines, :lines, :cursor, :tee
11
+
12
+ def initialize(**options)
13
+ @output = options[:output] || $stdout
14
+ @max_lines = options[:max_lines] || 5
15
+ @n_sticky = 0
16
+ @lines = []
17
+ @cursor = TTY::Cursor
18
+ @screen = TTY::Screen
19
+ @tee = options[:tee]
20
+ end
21
+
22
+ def clear
23
+ commit do
24
+ output.print(cursor.clear_lines(lines.count + @n_sticky))
25
+ @lines = []
26
+ @n_sticky = 0
27
+ end
28
+ end
29
+
30
+ def puts(line, sticky: false)
31
+ commit do
32
+ output.print(cursor.clear_lines(lines.count + 1))
33
+ if sticky
34
+ @n_sticky += 1
35
+ output.puts(truncated(line))
36
+ else
37
+ lines.shift if lines.count >= max_lines
38
+ lines << truncated(line)
39
+ end
40
+ output.puts(lines) # print non-sticky content
41
+ end
42
+ File.open(tee, "a") { |f| f << "#{line}\n" } if tee
43
+ end
44
+
45
+ def capture(header)
46
+ header_start = header.magenta.bold
47
+ header_success = "#{header} ✔".green.bold
48
+ header_error = "#{header} ✖".red.bold
49
+ puts(header_start, sticky: true)
50
+ yield if block_given?
51
+ clear
52
+ update_header(header_success)
53
+ rescue StandardError => e
54
+ update_header(header_error)
55
+ raise e
56
+ end
57
+
58
+ private
59
+
60
+ def update_header(header)
61
+ commit do
62
+ n = lines.count + @n_sticky
63
+ output.print(cursor.up(n) + header + cursor.column(0) + cursor.down(n))
64
+ end
65
+ end
66
+
67
+ def commit
68
+ CURSOR_LOCK.synchronize do
69
+ yield
70
+ output.flush
71
+ end
72
+ end
73
+
74
+ def truncated(msg)
75
+ msg.length > @screen.width ? "#{msg[...@screen.width - 3]}..." : msg
76
+ end
77
+
78
+ def ui_cls
79
+ self
80
+ end
81
+ end
82
+ end
@@ -5,13 +5,13 @@ module XCCache
5
5
  module UI
6
6
  @indent = 0
7
7
 
8
- class << self
8
+ module Mixin
9
9
  include Config::Mixin
10
10
  attr_accessor :indent
11
11
 
12
12
  def section(title, timing: false)
13
13
  start = Time.new if timing
14
- UI.puts(title)
14
+ ui_cls.puts(title)
15
15
  self.indent += 2
16
16
  res = yield if block_given?
17
17
  self.indent -= 2
@@ -22,25 +22,25 @@ module XCCache
22
22
  else
23
23
  "#{duration / 3600}h"
24
24
  end
25
- UI.puts("-> Finished: #{title.dark} (#{duration})")
25
+ ui_cls.puts("-> Finished: #{title.dark} (#{duration})")
26
26
  end
27
27
  res
28
28
  end
29
29
 
30
30
  def message(message)
31
- UI.puts(message) if config.verbose?
31
+ ui_cls.puts(message) if config.verbose?
32
32
  end
33
33
 
34
34
  def info(message)
35
- UI.puts(message)
35
+ ui_cls.puts(message)
36
36
  end
37
37
 
38
38
  def warn(message)
39
- UI.puts(message.yellow)
39
+ ui_cls.puts(message.yellow)
40
40
  end
41
41
 
42
42
  def error(message)
43
- UI.puts("[ERROR] #{message}".red)
43
+ ui_cls.puts("[ERROR] #{message}".red)
44
44
  end
45
45
 
46
46
  def error!(message)
@@ -51,6 +51,16 @@ module XCCache
51
51
  def puts(message)
52
52
  $stdout.puts("#{' ' * self.indent}#{message}")
53
53
  end
54
+
55
+ private
56
+
57
+ def ui_cls
58
+ UI
59
+ end
60
+ end
61
+
62
+ class << self
63
+ include Mixin
54
64
  end
55
65
  end
56
66
  end
@@ -17,14 +17,25 @@ module XCCache
17
17
 
18
18
  def run(*args, env: nil, **options)
19
19
  cmd = args.join(" ")
20
- UI.message("$ #{cmd}".cyan.dark) if config.verbose? && options[:log_cmd] != false
21
- return system(cmd) || (raise GeneralError, "Command '#{cmd}' failed") unless options[:capture]
22
20
 
23
21
  out, err = [], []
22
+ handle_out = options[:handle_out] || proc { |l| out << l }
23
+ handle_err = options[:handle_err] || proc { |l| err << l }
24
+ if (live_log = options[:live_log])
25
+ handle_out = proc { |l| live_log.puts(l) }
26
+ handle_err = proc { |l| live_log.puts(l) }
27
+ live_log.puts("$ #{cmd}") if options[:log_cmd] != false
28
+ elsif options[:log_cmd] != false
29
+ UI.message("$ #{cmd}".cyan.dark)
30
+ end
31
+
32
+ use_popen = options[:capture] || options[:handle_out] || options[:handle_err] || options[:live_log]
33
+ return system(cmd) || (raise GeneralError, "Command '#{cmd}' failed") unless use_popen
34
+
24
35
  popen3_args = env ? [env, cmd] : [cmd]
25
36
  Open3.popen3(*popen3_args) do |_stdin, stdout, stderr, wait_thr|
26
- stdout_thread = Thread.new { stdout.each { |l| out << l } }
27
- stderr_thread = Thread.new { stderr.each { |l| err << l } }
37
+ stdout_thread = Thread.new { stdout.each { |l| handle_out.call(l.strip) } }
38
+ stderr_thread = Thread.new { stderr.each { |l| handle_err.call(l.strip) } }
28
39
  [stdout_thread, stderr_thread].each(&:join)
29
40
  result = wait_thr.value
30
41
  result.exitstatus
@@ -32,6 +43,13 @@ module XCCache
32
43
  end
33
44
  [out.join("\n"), err.join("\n")]
34
45
  end
46
+
47
+ private
48
+
49
+ def log_cmd(cmd, live_log: nil)
50
+ return live_log.puts("$ #{cmd}") if live_log
51
+ UI.message("$ #{cmd}".cyan.dark)
52
+ end
35
53
  end
36
54
  end
37
55
  end
@@ -1,7 +1,8 @@
1
1
  module XCCache
2
2
  module SPM
3
3
  class Buildable
4
- attr_reader :name, :module_name, :pkg_dir, :pkg_desc, :sdk, :sdks, :config, :path, :tmpdir, :library_evolution
4
+ attr_reader :name, :module_name, :pkg_dir, :pkg_desc, :sdk, :sdks, :config, :path, :tmpdir, :library_evolution,
5
+ :live_log
5
6
  alias library_evolution? library_evolution
6
7
 
7
8
  def initialize(options = {})
@@ -17,6 +18,7 @@ module XCCache
17
18
  @tmpdir = options[:tmpdir]
18
19
  @library_evolution = options[:library_evolution]
19
20
  @sdks.each { |sdk| sdk.version = @ctx_desc.platforms[sdk.platform] } if @ctx_desc
21
+ @live_log = options[:live_log]
20
22
  end
21
23
 
22
24
  def build(options = {})
@@ -37,7 +39,11 @@ module XCCache
37
39
  cmd << "-Xswiftc" << "-emit-module-interface"
38
40
  cmd << "-Xswiftc" << "-no-verify-emitted-module-interface"
39
41
  end
40
- Sh.run(cmd)
42
+ sh(cmd)
43
+ end
44
+
45
+ def sh(cmd)
46
+ Sh.run(cmd, live_log: live_log)
41
47
  end
42
48
 
43
49
  def swift_build_args
@@ -21,7 +21,7 @@ module XCCache
21
21
  # -> WORKAROUND: Find the associated regular target and build it, then collect the tool binary
22
22
  # ---------------------------------------------------------------------------------
23
23
  associated_target = pkg_desc.targets.find { |t| t.direct_dependency_targets.include?(pkg_target) }
24
- UI.message(
24
+ live_log.info(
25
25
  "#{name.yellow.dark} is a macro target. " \
26
26
  "Will build the associated target #{associated_target.name.dark} to get the tool binary."
27
27
  )
@@ -30,7 +30,7 @@ module XCCache
30
30
  raise GeneralError, "Tool binary not exist at: #{binary_path}" unless binary_path.exist?
31
31
  binary_path.copy(to: path)
32
32
  FileUtils.chmod("+x", path)
33
- UI.info("-> Macro binary: #{path.to_s.dark}")
33
+ live_log.info("-> Macro binary: #{path}")
34
34
  end
35
35
 
36
36
  def products_dir
@@ -22,14 +22,19 @@ module XCCache
22
22
 
23
23
  def build(options = {})
24
24
  validate!
25
- targets = options.delete(:targets) || []
25
+ targets = (options.delete(:targets) || []).map { |t| t.split("/")[-1] }
26
26
  raise GeneralError, "No targets were specified" if targets.empty?
27
27
 
28
- targets.map { |t| t.split("/")[-1] }.each_with_index do |t, i|
29
- UI.section("\n▶ Building target: #{t} (#{i + 1}/#{targets.count})".bold.magenta) do
30
- build_target(**options, target: t)
28
+ Dir.create_tmpdir do |tmpdir|
29
+ targets.each_with_index do |t, i|
30
+ target_tmpdir = Dir.prepare(tmpdir / t)
31
+ log_dir = Dir.prepare(options[:log_dir] || target_tmpdir)
32
+ live_log = LiveLog.new(tee: log_dir / "build_#{t}.log")
33
+ live_log.capture("[#{i + 1}/#{targets.count}] Building target: #{t}") do
34
+ build_target(**options, target: t, live_log: live_log, tmpdir: target_tmpdir)
35
+ end
31
36
  rescue StandardError => e
32
- UI.error("Failed to build target: #{t}. Error: #{e}")
37
+ UI.error("Error: #{e}\n" + "For details, check out: #{live_log.tee}".yellow.bold)
33
38
  raise e unless Config.instance.ignore_build_errors?
34
39
  end
35
40
  end
@@ -52,20 +57,19 @@ module XCCache
52
57
  basename = options[:checksum] ? "#{target.name}-#{target.checksum}" : target.name
53
58
  binary_path = out_dir / "#{basename}#{ext}"
54
59
 
55
- Dir.create_tmpdir do |tmpdir|
56
- cls = target.macro? ? Macro : XCFramework
57
- cls.new(
58
- name: target.name,
59
- pkg_dir: root_dir,
60
- config: config,
61
- sdks: sdks,
62
- path: binary_path,
63
- tmpdir: tmpdir,
64
- pkg_desc: target_pkg_desc,
65
- ctx_desc: pkg_desc || target_pkg_desc,
66
- library_evolution: options[:library_evolution],
67
- ).build(**options)
68
- end
60
+ cls = target.macro? ? Macro : XCFramework
61
+ cls.new(
62
+ name: target.name,
63
+ pkg_dir: root_dir,
64
+ config: config,
65
+ sdks: sdks,
66
+ path: binary_path,
67
+ tmpdir: options[:tmpdir],
68
+ pkg_desc: target_pkg_desc,
69
+ ctx_desc: pkg_desc || target_pkg_desc,
70
+ library_evolution: options[:library_evolution],
71
+ live_log: options[:live_log],
72
+ ).build(**options)
69
73
  return if (symlinks_dir = options[:symlinks_dir]).nil?
70
74
  binary_path.symlink_to(symlinks_dir / target.name / "#{target.name}#{ext}")
71
75
  end
@@ -4,11 +4,12 @@ module XCCache
4
4
  class Proxy < Package
5
5
  class Executable
6
6
  REPO_URL = "https://github.com/trinhngocthuyen/xccache-proxy".freeze
7
- VERSION_OR_SHA = "1.0.0rc1".freeze
7
+ VERSION_OR_SHA = "1.0.0".freeze
8
8
 
9
9
  def run(cmd)
10
10
  env = { "FORCE_OUTPUT" => "console", "FORCE_COLOR" => "1" } if Config.instance.ansi?
11
11
  cmd = cmd.is_a?(Array) ? [bin_path.to_s] + cmd : [bin_path.to_s, cmd]
12
+ cmd << "--verbose" if Config.instance.verbose?
12
13
  Sh.run(cmd, env: env)
13
14
  end
14
15
 
@@ -4,10 +4,9 @@ module XCCache
4
4
  module SPM
5
5
  class FrameworkSlice < Buildable
6
6
  def build(_options = {})
7
- UI.section("Building slice: #{name} (#{config}, #{sdk})".bold) do
8
- swift_build
9
- create_framework
10
- end
7
+ live_log.puts("Building #{name}.framework (#{config}, #{sdk})".cyan, sticky: true)
8
+ swift_build
9
+ create_framework
11
10
  end
12
11
 
13
12
  private
@@ -21,7 +20,7 @@ module XCCache
21
20
  # WORKAROUND:
22
21
  # - Overriding resource_bundle_accessor.swift to add `Frameworks/<Target>.framework` to the search list
23
22
  # - Compiling this file into an `.o` file before using `libtool` to create the framework binary
24
- UI.message("Override resource_bundle_accessor")
23
+ live_log.info("Override resource_bundle_accessor")
25
24
  template_name = use_clang? ? "resource_bundle_accessor.m" : "resource_bundle_accessor.swift"
26
25
  source_path = tmpdir / File.basename(template_name)
27
26
  obj_path = products_dir / "#{module_name}.build" / "#{source_path.basename}.o"
@@ -44,7 +43,7 @@ module XCCache
44
43
  cmd << "-o" << obj_path.to_s
45
44
  cmd << source_path
46
45
  end
47
- Sh.run(cmd)
46
+ sh(cmd)
48
47
  end
49
48
 
50
49
  def create_framework
@@ -67,7 +66,7 @@ module XCCache
67
66
  cmd = ["libtool", "-static"]
68
67
  cmd << "-o" << "#{path}/#{module_name}"
69
68
  cmd << "-filelist" << objlist_path.to_s
70
- Sh.run(cmd)
69
+ sh(cmd)
71
70
  FileUtils.chmod("+x", path / module_name)
72
71
  end
73
72
 
@@ -85,7 +84,7 @@ module XCCache
85
84
  def create_modules
86
85
  copy_swiftmodules unless use_clang?
87
86
 
88
- UI.message("Creating framework modulemap")
87
+ live_log.info("Creating framework modulemap")
89
88
  Template.new("framework.modulemap").render(
90
89
  { :module_name => module_name, :target => name },
91
90
  save_to: modules_dir / "module.modulemap"
@@ -93,7 +92,7 @@ module XCCache
93
92
  end
94
93
 
95
94
  def copy_headers
96
- UI.message("Copying headers")
95
+ live_log.info("Copying headers")
97
96
  swift_header_paths = products_dir.glob("#{module_name}.build/*-Swift.h")
98
97
  paths = swift_header_paths + pkg_target.header_paths
99
98
  paths.each { |p| process_header(p) }
@@ -130,7 +129,7 @@ module XCCache
130
129
  end
131
130
 
132
131
  def copy_swiftmodules
133
- UI.message("Copying swiftmodules")
132
+ live_log.info("Copying swiftmodules")
134
133
  swiftmodule_dir = Dir.prepare("#{modules_dir}/#{module_name}.swiftmodule")
135
134
  swiftinterfaces = products_dir.glob("#{module_name}.build/#{module_name}.swiftinterface")
136
135
  to_copy = products_dir.glob("Modules/#{module_name}.*") + swiftinterfaces
@@ -141,7 +140,7 @@ module XCCache
141
140
 
142
141
  def copy_resource_bundles
143
142
  resolve_resource_symlinks
144
- UI.message("Copy resource bundle to framework: #{resource_bundle_product_path.basename}")
143
+ live_log.info("Copying resource bundle to framework: #{resource_bundle_product_path.basename}")
145
144
  resource_bundle_product_path.copy(to_dir: path)
146
145
  end
147
146
 
@@ -21,35 +21,32 @@ module XCCache
21
21
  tmp_existing_path = tmpdir / "existing.framework"
22
22
 
23
23
  slices.each(&:build)
24
- UI.section("Creating #{name}.xcframework from slices") do
25
- create_xcframework(from: slices.map(&:path), to: tmp_new_path)
26
- end
24
+ create_xcframework(from: slices.map(&:path), to: tmp_new_path)
27
25
 
28
26
  path.copy(to: tmp_existing_path) if path.exist? && merge_slices
29
27
  path.rmtree if path.exist?
30
28
 
31
29
  if merge_slices && tmp_existing_path.exist?
32
- UI.section("Merging #{name}.xcframework with existing slices") do
33
- framework_paths =
34
- [tmp_new_path, tmp_existing_path]
35
- .flat_map { |p| p.glob("*/*.framework") }
36
- .uniq { |p| p.parent.basename.to_s } # uniq by id (ex. ios-arm64), preferred new ones
37
- create_xcframework(from: framework_paths, to: path)
38
- end
30
+ framework_paths =
31
+ [tmp_new_path, tmp_existing_path]
32
+ .flat_map { |p| p.glob("*/*.framework") }
33
+ .uniq { |p| p.parent.basename.to_s } # uniq by id (ex. ios-arm64), preferred new ones
34
+ create_xcframework(from: framework_paths, to: path)
39
35
  else
40
36
  path.parent.mkpath
41
37
  tmp_new_path.copy(to: path)
42
38
  end
43
- UI.info("-> XCFramework: #{path.to_s.dark}")
39
+ live_log.info("-> XCFramework: #{path}")
44
40
  end
45
41
 
46
42
  def create_xcframework(options = {})
43
+ live_log.info("Creating xcframework from slices")
47
44
  cmd = ["xcodebuild", "-create-xcframework"]
48
45
  cmd << "-allow-internal-distribution" unless library_evolution?
49
46
  cmd << "-output" << options[:to]
50
47
  options[:from].each { |p| cmd << "-framework" << p }
51
48
  cmd << "> /dev/null" # Only care about errors
52
- Sh.run(cmd)
49
+ sh(cmd)
53
50
  end
54
51
  end
55
52
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xccache
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thuyen Trinh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-06-10 00:00:00.000000000 Z
11
+ date: 2025-06-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: claide
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: tty-cursor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: tty-screen
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: xcodeproj
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -91,6 +119,7 @@ files:
91
119
  - lib/xccache/core/error.rb
92
120
  - lib/xccache/core/git.rb
93
121
  - lib/xccache/core/hash.rb
122
+ - lib/xccache/core/live_log.rb
94
123
  - lib/xccache/core/lockfile.rb
95
124
  - lib/xccache/core/log.rb
96
125
  - lib/xccache/core/parallel.rb