xccache 0.0.1a1 → 0.0.1a2
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 +54 -0
- data/lib/xccache/assets/templates/cachemap.js.template +94 -0
- data/lib/xccache/assets/templates/cachemap.style.css.template +92 -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 +147 -0
- data/lib/xccache/cache/cachemap.rb +60 -0
- data/lib/xccache/command/build.rb +35 -0
- data/lib/xccache/command/pkg/build.rb +34 -0
- data/lib/xccache/command/rollback.rb +13 -0
- data/lib/xccache/command/use.rb +18 -0
- data/lib/xccache/command.rb +11 -1
- data/lib/xccache/core/cacheable.rb +28 -0
- data/lib/xccache/core/config.rb +82 -0
- data/lib/xccache/core/error.rb +7 -0
- data/lib/xccache/core/git.rb +19 -0
- data/lib/xccache/core/hash.rb +21 -0
- data/lib/xccache/core/lockfile.rb +40 -0
- data/lib/xccache/core/log.rb +41 -0
- data/lib/xccache/core/sh.rb +50 -0
- data/lib/xccache/core/syntax/hash.rb +31 -0
- data/lib/xccache/core/syntax/json.rb +16 -0
- data/lib/xccache/core/syntax/yml.rb +16 -0
- data/lib/xccache/core/syntax.rb +1 -0
- data/lib/xccache/core/system.rb +54 -0
- data/lib/xccache/core.rb +1 -0
- data/lib/xccache/framework/slice.rb +183 -0
- data/lib/xccache/framework/xcframework.rb +51 -0
- data/lib/xccache/installer/build.rb +27 -0
- data/lib/xccache/installer/rollback.rb +36 -0
- data/lib/xccache/installer/use.rb +27 -0
- data/lib/xccache/installer.rb +67 -0
- data/lib/xccache/main.rb +3 -1
- data/lib/xccache/spm/desc/base.rb +61 -0
- data/lib/xccache/spm/desc/dep.rb +40 -0
- data/lib/xccache/spm/desc/desc.rb +110 -0
- data/lib/xccache/spm/desc/product.rb +36 -0
- data/lib/xccache/spm/desc/target.rb +138 -0
- data/lib/xccache/spm/desc.rb +1 -0
- data/lib/xccache/spm/mixin.rb +9 -0
- data/lib/xccache/spm/pkg/base.rb +103 -0
- data/lib/xccache/spm/pkg/umbrella/build.rb +30 -0
- data/lib/xccache/spm/pkg/umbrella/cachemap.rb +93 -0
- data/lib/xccache/spm/pkg/umbrella/descs.rb +46 -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.rb +81 -0
- data/lib/xccache/spm/pkg.rb +1 -0
- data/lib/xccache/spm.rb +1 -0
- data/lib/xccache/swift/sdk.rb +30 -0
- data/lib/xccache/swift/swiftc.rb +16 -0
- data/lib/xccache/utils/template.rb +21 -0
- data/lib/xccache/xcodeproj/pkg.rb +69 -0
- data/lib/xccache/xcodeproj/pkg_product_dependency.rb +19 -0
- data/lib/xccache/xcodeproj/project.rb +63 -0
- data/lib/xccache/xcodeproj/target.rb +50 -0
- data/lib/xccache/xcodeproj.rb +2 -0
- metadata +72 -2
@@ -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,82 @@
|
|
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
|
+
def sandbox
|
20
|
+
@sandbox = Dir.prepare("xccache").expand_path
|
21
|
+
end
|
22
|
+
|
23
|
+
def spm_sandbox
|
24
|
+
@spm_sandbox ||= Dir.prepare(sandbox / "packages").expand_path
|
25
|
+
end
|
26
|
+
|
27
|
+
def spm_binaries_frameworks_dir
|
28
|
+
@spm_binaries_frameworks_dir ||= Dir.prepare(spm_umbrella_sandbox / "binaries")
|
29
|
+
end
|
30
|
+
|
31
|
+
def spm_build_dir
|
32
|
+
@spm_build_dir ||= spm_umbrella_sandbox / ".build"
|
33
|
+
end
|
34
|
+
|
35
|
+
def spm_artifacts_dir
|
36
|
+
@spm_artifacts_dir ||= spm_build_dir / "artifacts"
|
37
|
+
end
|
38
|
+
|
39
|
+
def spm_umbrella_sandbox
|
40
|
+
@spm_umbrella_sandbox ||= Dir.prepare(spm_sandbox / "umbrella")
|
41
|
+
end
|
42
|
+
|
43
|
+
def spm_metadata_dir
|
44
|
+
@spm_metadata_dir ||= Dir.prepare(spm_sandbox / "metadata")
|
45
|
+
end
|
46
|
+
|
47
|
+
def lockfile
|
48
|
+
@lockfile ||= Lockfile.new(Pathname("xccache.lock").expand_path)
|
49
|
+
end
|
50
|
+
|
51
|
+
def cachemap
|
52
|
+
@cachemap ||= Cache::Cachemap.new(sandbox / "cachemap.json")
|
53
|
+
end
|
54
|
+
|
55
|
+
def projects
|
56
|
+
@projects ||= Pathname(".").glob("*.xcodeproj").map do |p|
|
57
|
+
Xcodeproj::Project.open(p)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def project_targets
|
62
|
+
projects.flat_map(&:targets)
|
63
|
+
end
|
64
|
+
|
65
|
+
def ignore_list
|
66
|
+
raw["ignore"] || []
|
67
|
+
end
|
68
|
+
|
69
|
+
def ignore?(item)
|
70
|
+
return true if ignore_local? && lockfile.local_pkg_slugs.include?(item.split("/").first)
|
71
|
+
ignore_list.any? { |p| File.fnmatch(p, item) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def ignore_local?
|
75
|
+
raw["ignore_local"]
|
76
|
+
end
|
77
|
+
|
78
|
+
def ignore_build_errors?
|
79
|
+
raw["ignore_build_errors"]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module XCCache
|
2
|
+
class Git
|
3
|
+
attr_reader :root
|
4
|
+
|
5
|
+
def initialize(root)
|
6
|
+
@root = Pathname(root)
|
7
|
+
end
|
8
|
+
|
9
|
+
def sha
|
10
|
+
run("rev-parse --short HEAD", capture: true, log_cmd: false)[0].strip
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def run(cmd, options = {})
|
16
|
+
Sh.run("git -C #{root} #{cmd}", options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
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,41 @@
|
|
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)
|
13
|
+
UI.puts(title)
|
14
|
+
self.indent += 2
|
15
|
+
res = yield if block_given?
|
16
|
+
self.indent -= 2
|
17
|
+
res
|
18
|
+
end
|
19
|
+
|
20
|
+
def message(message)
|
21
|
+
UI.puts(message) if config.verbose?
|
22
|
+
end
|
23
|
+
|
24
|
+
def info(message)
|
25
|
+
UI.puts(message)
|
26
|
+
end
|
27
|
+
|
28
|
+
def warn(message)
|
29
|
+
UI.puts(message.yellow)
|
30
|
+
end
|
31
|
+
|
32
|
+
def error(message)
|
33
|
+
UI.puts("[ERROR] #{message}".red)
|
34
|
+
end
|
35
|
+
|
36
|
+
def puts(message)
|
37
|
+
$stdout.puts("#{' ' * self.indent}#{message}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,50 @@
|
|
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(cmd, options = {})
|
19
|
+
cmd = cmd.join(" ") if cmd.is_a?(Array)
|
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
|
+
Open3.popen3(cmd) do |_stdin, stdout, stderr, wait_thr|
|
39
|
+
stdout_thread = Thread.new { stdout.each { |l| handle_out.call(l) } }
|
40
|
+
stderr_thread = Thread.new { stderr.each { |l| handle_err.call(l) } }
|
41
|
+
[stdout_thread, stderr_thread].each(&:join)
|
42
|
+
result = wait_thr.value
|
43
|
+
result.exitstatus
|
44
|
+
raise ExecError, "Command '#{cmd}' failed with status: #{result.exitstatus}" unless result.success?
|
45
|
+
end
|
46
|
+
[out.join("\n"), err.join("\n")]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
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,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 }
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "digest"
|
2
|
+
|
3
|
+
class String
|
4
|
+
def c99extidentifier
|
5
|
+
gsub(/[^a-zA-Z0-9.]/, "_")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Dir
|
10
|
+
def self.prepare(dir)
|
11
|
+
dir = Pathname(dir)
|
12
|
+
dir.mkpath
|
13
|
+
dir
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.create_tmpdir
|
17
|
+
dir = Pathname(Dir.mktmpdir("xccache"))
|
18
|
+
res = block_given? ? (yield dir) : dir
|
19
|
+
dir.rmtree if block_given?
|
20
|
+
res
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.git?(dir)
|
24
|
+
XCCache::Sh.capture_output("git -C #{dir} rev-parse --git-dir") == ".git"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Pathname
|
29
|
+
def symlink_to(dst)
|
30
|
+
dst = Pathname(dst)
|
31
|
+
dst.rmtree if dst.symlink?
|
32
|
+
dst.parent.mkpath
|
33
|
+
File.symlink(expand_path, dst)
|
34
|
+
end
|
35
|
+
|
36
|
+
def copy(to: nil, to_dir: nil)
|
37
|
+
dst = to || (Pathname(to_dir) / basename)
|
38
|
+
dst.rmtree if dst.exist? || dst.symlink?
|
39
|
+
FileUtils.copy_entry(self, dst)
|
40
|
+
dst
|
41
|
+
end
|
42
|
+
|
43
|
+
def checksum
|
44
|
+
hasher = Digest::SHA256.new
|
45
|
+
glob("**/*").reject { |p| p.directory? || p.symlink? }.sort.each do |p|
|
46
|
+
p.open("rb") do |f|
|
47
|
+
while (chunk = f.read(65_536)) # Read 64KB chunks
|
48
|
+
hasher.update(chunk)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
hasher.hexdigest[...8]
|
53
|
+
end
|
54
|
+
end
|
data/lib/xccache/core.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Dir["#{__dir__}/#{File.basename(__FILE__, '.rb')}/*.rb"].sort.each { |f| require f }
|
@@ -0,0 +1,183 @@
|
|
1
|
+
require "xccache/utils/template"
|
2
|
+
|
3
|
+
module XCCache
|
4
|
+
class Framework
|
5
|
+
class Slice
|
6
|
+
attr_reader :name, :module_name, :pkg_dir, :pkg_desc, :sdk, :config, :path, :tmpdir
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@name = options[:name]
|
10
|
+
@module_name = @name.c99extidentifier
|
11
|
+
@pkg_dir = Pathname(options[:pkg_dir] || ".").expand_path
|
12
|
+
@pkg_desc = options[:pkg_desc]
|
13
|
+
@sdk = options[:sdk]
|
14
|
+
@config = options[:config] || "debug"
|
15
|
+
@path = options[:path]
|
16
|
+
@tmpdir = options[:tmpdir]
|
17
|
+
end
|
18
|
+
|
19
|
+
def build
|
20
|
+
UI.section("Building slice: #{name} (#{config}, #{sdk})".bold) do
|
21
|
+
cmd = ["swift", "build"] + swift_build_args
|
22
|
+
cmd << "--package-path" << pkg_dir
|
23
|
+
cmd << "--target" << name
|
24
|
+
cmd << "--sdk" << sdk.sdk_path
|
25
|
+
# Workaround for swiftinterface emission
|
26
|
+
# https://github.com/swiftlang/swift/issues/64669#issuecomment-1535335601
|
27
|
+
cmd << "-Xswiftc" << "-enable-library-evolution"
|
28
|
+
cmd << "-Xswiftc" << "-alias-module-names-in-module-interface"
|
29
|
+
cmd << "-Xswiftc" << "-emit-module-interface"
|
30
|
+
cmd << "-Xswiftc" << "-no-verify-emitted-module-interface"
|
31
|
+
Sh.run(cmd, suppress_err: /(dependency '.*' is not used by any target|unable to create symbolic link)/)
|
32
|
+
create_framework
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def override_resource_bundle_accessor
|
39
|
+
# By default, Swift generates resource_bundle_accessor.swift for targets having resources
|
40
|
+
# (Check .build/<Target>.build/DerivedSources/resource_bundle_accessor.swift)
|
41
|
+
# This enables accessing the resource bundle via `Bundle.module`.
|
42
|
+
# However, `Bundle.module` expects the resource bundle to be under the app bundle,
|
43
|
+
# which is not the case for binary targets. Instead the bundle is under `Frameworks/<Target>.framework`
|
44
|
+
# WORKAROUND:
|
45
|
+
# - Overriding resource_bundle_accessor.swift to add `Frameworks/<Target>.framework` to the search list
|
46
|
+
# - Compiling this file into an `.o` file before using `libtool` to create the framework binary
|
47
|
+
UI.message("Override resource_bundle_accessor")
|
48
|
+
template_name = use_clang? ? "resource_bundle_accessor.m" : "resource_bundle_accessor.swift"
|
49
|
+
source_path = tmpdir / File.basename(template_name)
|
50
|
+
obj_path = products_dir / "#{module_name}.build" / "#{source_path.basename}.o"
|
51
|
+
Template.new(template_name).render(
|
52
|
+
{ :pkg => pkg_target.pkg_name, :target => name, :module_name => module_name },
|
53
|
+
save_to: source_path
|
54
|
+
)
|
55
|
+
|
56
|
+
if use_clang?
|
57
|
+
cmd = ["xcrun", "clang"]
|
58
|
+
cmd << "-x" << "objective-c"
|
59
|
+
cmd << "-target" << sdk.triple << "-isysroot" << sdk.sdk_path
|
60
|
+
cmd << "-o" << obj_path.to_s
|
61
|
+
cmd << "-c" << source_path
|
62
|
+
else
|
63
|
+
cmd = ["xcrun", "swiftc"]
|
64
|
+
cmd << "-emit-library" << "-emit-object"
|
65
|
+
cmd << "-module-name" << module_name
|
66
|
+
cmd << "-target" << sdk.triple << "-sdk" << sdk.sdk_path
|
67
|
+
cmd << "-o" << obj_path.to_s
|
68
|
+
cmd << source_path
|
69
|
+
end
|
70
|
+
Sh.run(cmd)
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_framework
|
74
|
+
override_resource_bundle_accessor if resource_bundle_product_path.exist?
|
75
|
+
create_info_plist
|
76
|
+
create_framework_binary
|
77
|
+
create_headers
|
78
|
+
create_modules
|
79
|
+
copy_resource_bundles if resource_bundle_product_path.exist?
|
80
|
+
end
|
81
|
+
|
82
|
+
def create_framework_binary
|
83
|
+
# Write .o file list into a file
|
84
|
+
obj_paths = products_dir.glob("#{module_name}.build/**/*.o")
|
85
|
+
raise GeneralError, "Detected no object files for #{name}" if obj_paths.empty?
|
86
|
+
|
87
|
+
objlist_path = tmpdir / "objects.txt"
|
88
|
+
objlist_path.write(obj_paths.map(&:to_s).join("\n"))
|
89
|
+
|
90
|
+
cmd = ["libtool", "-static"]
|
91
|
+
cmd << "-o" << "#{path}/#{module_name}"
|
92
|
+
cmd << "-filelist" << objlist_path.to_s
|
93
|
+
Sh.run(cmd)
|
94
|
+
FileUtils.chmod("+x", path / module_name)
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_info_plist
|
98
|
+
Template.new("framework.info.plist").render(
|
99
|
+
{ :module_name => module_name },
|
100
|
+
save_to: path / "Info.plist",
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
def create_headers
|
105
|
+
Dir.prepare(path / "Headers")
|
106
|
+
copy_headers if use_clang?
|
107
|
+
end
|
108
|
+
|
109
|
+
def create_modules
|
110
|
+
Dir.prepare(path / "Modules")
|
111
|
+
return copy_swiftmodules unless use_clang?
|
112
|
+
|
113
|
+
UI.message("Creating framework modulemap")
|
114
|
+
Template.new("framework.modulemap").render(
|
115
|
+
{ :module_name => module_name },
|
116
|
+
save_to: path / "Modules" / "module.modulemap"
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
def copy_headers
|
121
|
+
UI.message("Copying headers")
|
122
|
+
framework_headers_path = path / "Headers"
|
123
|
+
umbrella_header_content =
|
124
|
+
pkg_target
|
125
|
+
.header_paths
|
126
|
+
.map { |p| p.copy(to_dir: framework_headers_path) }
|
127
|
+
.map { |p| "#include \"#{p.basename}\"" }
|
128
|
+
.join("\n")
|
129
|
+
(framework_headers_path / "#{module_name}-umbrella.h").write(umbrella_header_content)
|
130
|
+
end
|
131
|
+
|
132
|
+
def copy_swiftmodules
|
133
|
+
UI.message("Copying swiftmodules")
|
134
|
+
swiftmodule_dir = Dir.prepare("#{path}/Modules/#{module_name}.swiftmodule")
|
135
|
+
swiftinterfaces = products_dir.glob("#{module_name}.build/#{module_name}.swiftinterface")
|
136
|
+
to_copy = products_dir.glob("Modules/#{module_name}.*") + swiftinterfaces
|
137
|
+
to_copy.each do |p|
|
138
|
+
p.copy(to: swiftmodule_dir / p.basename.sub(module_name, sdk.triple))
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def copy_resource_bundles
|
143
|
+
resolve_resource_symlinks
|
144
|
+
UI.message("Copy resource bundle to framework: #{resource_bundle_product_path.basename}")
|
145
|
+
resource_bundle_product_path.copy(to_dir: path)
|
146
|
+
end
|
147
|
+
|
148
|
+
def resolve_resource_symlinks
|
149
|
+
# Well, Xcode seems to well handle symlinks in resources. In xcodebuild log, you would see something like:
|
150
|
+
# CpResource: builtin-copy ... -resolve-src-symlinks
|
151
|
+
# But this is not the case if we build with `swift build`. Here, we have to manually handle it
|
152
|
+
resource_bundle_product_path.glob("**/*").select(&:symlink?).reject(&:exist?).each do |p|
|
153
|
+
UI.message("Resolve resource symlink: #{p}")
|
154
|
+
original = pkg_target.resource_paths.find { |rp| rp.symlink? && rp.readlink == p.readlink }
|
155
|
+
original&.realpath&.copy(to: p)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def products_dir
|
160
|
+
@products_dir ||= pkg_dir / ".build" / sdk.triple / config
|
161
|
+
end
|
162
|
+
|
163
|
+
def swift_build_args
|
164
|
+
[
|
165
|
+
"--configuration", config,
|
166
|
+
"--triple", sdk.triple,
|
167
|
+
]
|
168
|
+
end
|
169
|
+
|
170
|
+
def use_clang?
|
171
|
+
pkg_target.use_clang?
|
172
|
+
end
|
173
|
+
|
174
|
+
def pkg_target
|
175
|
+
@pkg_target ||= pkg_desc.get_target(name)
|
176
|
+
end
|
177
|
+
|
178
|
+
def resource_bundle_product_path
|
179
|
+
@resource_bundle_product_path ||= products_dir / pkg_target.bundle_name
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module XCCache
|
2
|
+
class Framework
|
3
|
+
class XCFramework
|
4
|
+
attr_reader :name, :module_name, :pkg_dir, :pkg_desc, :config, :sdks, :path
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@name = options[:name]
|
8
|
+
@module_name = @name.c99extidentifier
|
9
|
+
@pkg_dir = options[:pkg_dir]
|
10
|
+
@pkg_desc = options[:pkg_desc]
|
11
|
+
@config = options[:config]
|
12
|
+
@sdks = options[:sdks]
|
13
|
+
@path = options[:path]
|
14
|
+
end
|
15
|
+
|
16
|
+
def create
|
17
|
+
slices.each(&:build)
|
18
|
+
UI.section("Creating #{name}.xcframework from slices") do
|
19
|
+
path.rmtree if path.exist?
|
20
|
+
|
21
|
+
cmd = ["xcodebuild", "-create-xcframework"]
|
22
|
+
cmd << "-output" << path
|
23
|
+
slices.each { |slice| cmd << "-framework" << slice.path }
|
24
|
+
Sh.run(cmd)
|
25
|
+
end
|
26
|
+
tmpdir.rmtree
|
27
|
+
|
28
|
+
# TODO: Should we dispose tmpdir here as well?
|
29
|
+
end
|
30
|
+
|
31
|
+
def tmpdir
|
32
|
+
@tmpdir ||= Dir.create_tmpdir
|
33
|
+
end
|
34
|
+
|
35
|
+
def slices
|
36
|
+
@slices ||= sdks.map do |s|
|
37
|
+
sdk = Swift::Sdk.new(s)
|
38
|
+
Framework::Slice.new(
|
39
|
+
name: name,
|
40
|
+
pkg_dir: pkg_dir,
|
41
|
+
pkg_desc: pkg_desc,
|
42
|
+
sdk: sdk,
|
43
|
+
config: config,
|
44
|
+
path: Dir.prepare(tmpdir / sdk.triple / "#{module_name}.framework"),
|
45
|
+
tmpdir: tmpdir,
|
46
|
+
)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "xccache/spm"
|
2
|
+
|
3
|
+
module XCCache
|
4
|
+
class Installer
|
5
|
+
class Build < Installer
|
6
|
+
def initialize(options = {})
|
7
|
+
super
|
8
|
+
@targets = options[:targets]
|
9
|
+
@sdk = options[:sdk]
|
10
|
+
@recursive = options[:recursive]
|
11
|
+
end
|
12
|
+
|
13
|
+
def install!
|
14
|
+
perform_install do
|
15
|
+
umbrella_pkg.build(
|
16
|
+
targets: @targets,
|
17
|
+
sdk: @sdk,
|
18
|
+
out_dir: config.spm_binaries_frameworks_dir,
|
19
|
+
checksum: true,
|
20
|
+
recursive: @recursive,
|
21
|
+
skip_resolve: @skip_resolving_dependencies,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|