apple_frameworks 1.0.0
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 +7 -0
- data/lib/apple_frameworks/framework.rb +96 -0
- data/lib/apple_frameworks/version.rb +3 -0
- data/lib/apple_frameworks/xcframework.rb +92 -0
- data/lib/apple_frameworks.rb +6 -0
- data/spec/framework_spec.rb +110 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/xcframework_spec.rb +118 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 88a784f4579a6fe0d1c01d20cee23743ff51987026fe70102a34f3b2a568e6ff
|
4
|
+
data.tar.gz: b98904da83eb50018c769c6443d7149da20ac759d551ab6f83b4c8259742e53d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 60ddf21feb22a51447f0f22c11b820783424716037047737c13f400f6fc096ac2a15a6dafb69f2acceb10bba7957b828420c45adb40613c634996c7706d6ad1e
|
7
|
+
data.tar.gz: aeebac5804a5d7d08df84018c1fb1073cdf9ce226e76d87a3cb80a43137e8578613eecef1f119e0929ae44cac923490878e9bb5cde36d451023094b88264c4cd
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module AppleFrameworks
|
2
|
+
# Creates an Apple (iOS or macOS) Framework from an existing library (`.a`
|
3
|
+
# file).
|
4
|
+
#
|
5
|
+
# The Framework is built up with a directory structure:
|
6
|
+
#
|
7
|
+
# ```
|
8
|
+
# LibraryName.Framework
|
9
|
+
# Info.plist
|
10
|
+
# library_name (the actual dynamic lib)
|
11
|
+
# Headers
|
12
|
+
# library_name
|
13
|
+
# (all the headers)
|
14
|
+
# ```
|
15
|
+
#
|
16
|
+
class Framework
|
17
|
+
# - parameter framework_name: The name of the resulting framework.
|
18
|
+
# - parameter parent_directory: The directory in which to create the
|
19
|
+
# framework.
|
20
|
+
# - parameter library: The library itself; the `.a` file.
|
21
|
+
# - parameter headers_directory: The directory which includes the headers.
|
22
|
+
# Normally located in the `include/` directory.
|
23
|
+
#
|
24
|
+
def initialize(framework_name, parent_directory, library, headers_directory)
|
25
|
+
@framework_name = framework_name
|
26
|
+
@parent_directory = parent_directory
|
27
|
+
@library = library
|
28
|
+
@headers_directory = headers_directory
|
29
|
+
|
30
|
+
@framework_directory = File.join(
|
31
|
+
@parent_directory,
|
32
|
+
"#{@framework_name}.framework"
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Generates the `.framework` bundle.
|
37
|
+
#
|
38
|
+
def build
|
39
|
+
create_directories
|
40
|
+
copy_lib
|
41
|
+
copy_headers
|
42
|
+
generate_plist
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def create_directories
|
48
|
+
if File.directory?(@framework_directory)
|
49
|
+
raise "Framework already exists"
|
50
|
+
end
|
51
|
+
|
52
|
+
if File.file?(@framework_directory)
|
53
|
+
raise "File already exists at Framework destination"
|
54
|
+
end
|
55
|
+
|
56
|
+
FileUtils.mkdir_p(File.join(@framework_directory, "Headers"))
|
57
|
+
end
|
58
|
+
|
59
|
+
def copy_lib
|
60
|
+
FileUtils.cp(@library, File.join(@framework_directory, @framework_name))
|
61
|
+
end
|
62
|
+
|
63
|
+
def copy_headers
|
64
|
+
FileUtils.cp_r(File.join(@headers_directory), File.join(@framework_directory, "Headers", @framework_name))
|
65
|
+
end
|
66
|
+
|
67
|
+
def generate_plist
|
68
|
+
filepath = File.join(@framework_directory, "Info.plist")
|
69
|
+
|
70
|
+
plist_contents = <<~XML
|
71
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
72
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
73
|
+
<plist version="1.0">
|
74
|
+
<dict>
|
75
|
+
<key>CFBundleName</key>
|
76
|
+
<string>#{@framework_name}</string>
|
77
|
+
<key>CFBundleDevelopmentRegion</key>
|
78
|
+
<string>en</string>
|
79
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
80
|
+
<string>6.0</string>
|
81
|
+
<key>CFBundlePackageType</key>
|
82
|
+
<string>FMWK</string>
|
83
|
+
<key>CFBundleIdentifier</key>
|
84
|
+
<string>#{@framework_name}.#{@framework_name}</string>
|
85
|
+
<key>CFBundleExecutable</key>
|
86
|
+
<string>#{@framework_name}</string>
|
87
|
+
<key>CFBundleVersion</key>
|
88
|
+
<string>1</string>
|
89
|
+
</dict>
|
90
|
+
</plist>
|
91
|
+
XML
|
92
|
+
|
93
|
+
File.open(filepath, "w") { |file| file.puts(plist_contents) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module AppleFrameworks
|
2
|
+
# Creates an Apple (iOS or macOS) XCFramework from a set of existing
|
3
|
+
# frameworks (`.framework`).
|
4
|
+
#
|
5
|
+
# XCFramework as a concept is newer than Frameworks. It was introduced due to
|
6
|
+
# targets supporting more architectures. For example iOS Simulator now
|
7
|
+
# supports the same architecture as iOS itself (arm64). Previously a Fat or
|
8
|
+
# Universal Library could contain both x64 and arm64 architectures, thereby
|
9
|
+
# supporting both simulator and real devices, but with the Fat format this
|
10
|
+
# isn't possible anymore for arm64 and arm64. Enter XCFramework which simply
|
11
|
+
# contains many frameworks of the same library but for different targets and
|
12
|
+
# architectures.
|
13
|
+
#
|
14
|
+
# To create a `.framework` from a `.a` library, see
|
15
|
+
# `AppleFrameworks::Framework`.
|
16
|
+
#
|
17
|
+
# The XCFramework is built up with a directory structure, including each
|
18
|
+
# Framework for each individual target:
|
19
|
+
#
|
20
|
+
# ```
|
21
|
+
# LibraryName.XCFramework
|
22
|
+
# Info.plist
|
23
|
+
# ios-arm64
|
24
|
+
# LibraryName.framework
|
25
|
+
# ios-x86_64-simulator
|
26
|
+
# LibraryName.framework
|
27
|
+
# ```
|
28
|
+
#
|
29
|
+
# Note: Binaries with multiple platforms are not supported by Xcode.
|
30
|
+
#
|
31
|
+
class XCFramework
|
32
|
+
class BuildUsingXcodeFailure < StandardError; end
|
33
|
+
|
34
|
+
# - parameter framework_name: The name of the resulting XCFramework.
|
35
|
+
# - parameter parent_directory: The directory in which to create the
|
36
|
+
# framework.
|
37
|
+
# - parameter framework_paths: An array of paths to the `.framework`
|
38
|
+
# bundles.
|
39
|
+
#
|
40
|
+
def initialize(framework_name, parent_directory, framework_paths)
|
41
|
+
@framework_name = framework_name
|
42
|
+
@framework_paths = framework_paths
|
43
|
+
@parent_directory = parent_directory
|
44
|
+
|
45
|
+
@framework_directory = File.join(
|
46
|
+
@parent_directory,
|
47
|
+
"#{@framework_name}.xcframework"
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Uses the `xcodebuild -create-xcframework` command to create the
|
52
|
+
# XCFramework.
|
53
|
+
#
|
54
|
+
# Note: this will require Xcode and its command line tools to be installed.
|
55
|
+
#
|
56
|
+
def build_using_xcode
|
57
|
+
validations
|
58
|
+
|
59
|
+
framework_args = @framework_paths
|
60
|
+
.map { |path| "-framework #{path}" }
|
61
|
+
.join(" ")
|
62
|
+
|
63
|
+
FileUtils.mkdir_p(@parent_directory)
|
64
|
+
output_path = File.join(@parent_directory, "#{@framework_name}.xcframework")
|
65
|
+
output_args = "-output #{output_path}"
|
66
|
+
|
67
|
+
logfile = Tempfile.new(['xcframework', '.log'])
|
68
|
+
|
69
|
+
cmd = "xcodebuild -create-xcframework #{framework_args} #{output_args}"
|
70
|
+
|
71
|
+
system("#{cmd} >#{logfile.path} 2>&1") ||
|
72
|
+
raise(BuildUsingXcodeFailure.new(File.read(logfile).strip))
|
73
|
+
ensure
|
74
|
+
if logfile
|
75
|
+
logfile.close
|
76
|
+
logfile.delete
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def validations
|
83
|
+
if File.directory?(@framework_directory)
|
84
|
+
raise "XCFramework already exists"
|
85
|
+
end
|
86
|
+
|
87
|
+
if File.file?(@framework_directory)
|
88
|
+
raise "File already exists at XCFramework destination"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
RSpec.describe AppleFrameworks::Framework do
|
2
|
+
let(:framework_name) { "framework_name" }
|
3
|
+
let(:parent_directory) { Bundler.root.join("tmp", "framework_name") }
|
4
|
+
let(:library) { Bundler.root.join("spec", "fixtures", "fake.a") }
|
5
|
+
let(:headers_directory) { Bundler.root.join("spec", "fixtures", "headers") }
|
6
|
+
let(:described_instance) { described_class.new(framework_name, parent_directory, library, headers_directory) }
|
7
|
+
|
8
|
+
describe "#build" do
|
9
|
+
subject { described_instance.build }
|
10
|
+
before { FileUtils.rm_rf(parent_directory) }
|
11
|
+
after { FileUtils.rm_rf(parent_directory) }
|
12
|
+
|
13
|
+
context "when there is already a framework in the parent_directory" do
|
14
|
+
before do
|
15
|
+
FileUtils.mkdir_p(File.join(parent_directory, "#{framework_name}.framework"))
|
16
|
+
|
17
|
+
expect(File.directory?(parent_directory))
|
18
|
+
.to eq(true)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "raises an error" do
|
22
|
+
expect { subject }
|
23
|
+
.to raise_error("Framework already exists")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "when there is a file at the target parent_directory" do
|
28
|
+
before do
|
29
|
+
FileUtils.mkdir_p(parent_directory)
|
30
|
+
FileUtils.touch(File.join(parent_directory, "#{framework_name}.framework"))
|
31
|
+
|
32
|
+
expect(File.file?(File.join(parent_directory, "#{framework_name}.framework")))
|
33
|
+
.to eq(true)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "raises an error" do
|
37
|
+
expect { subject }
|
38
|
+
.to raise_error("File already exists at Framework destination")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context "when there isn't a directory at parent_directory" do
|
43
|
+
before do
|
44
|
+
expect(File.directory?(parent_directory))
|
45
|
+
.to eq(false)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "creates the directory" do
|
49
|
+
subject
|
50
|
+
|
51
|
+
expect(File.directory?(parent_directory))
|
52
|
+
.to eq(true)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "copies the library" do
|
56
|
+
subject
|
57
|
+
|
58
|
+
expected_library_path = File.join(parent_directory, "#{framework_name}.framework", framework_name)
|
59
|
+
a = Digest::MD5.hexdigest(File.read(library))
|
60
|
+
b = Digest::MD5.hexdigest(File.read(expected_library_path))
|
61
|
+
|
62
|
+
expect(a).to eq(b)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "copies the headers" do
|
66
|
+
subject
|
67
|
+
|
68
|
+
a1 = File.join(headers_directory, "a.h")
|
69
|
+
b1 = File.join(headers_directory, "b.h")
|
70
|
+
|
71
|
+
a2 = File.join(parent_directory, "#{framework_name}.framework", "Headers", framework_name, "a.h")
|
72
|
+
b2 = File.join(parent_directory, "#{framework_name}.framework", "Headers", framework_name, "b.h")
|
73
|
+
|
74
|
+
expect(File.read(a1)).to eq(File.read(a2))
|
75
|
+
expect(File.read(b1)).to eq(File.read(b2))
|
76
|
+
end
|
77
|
+
|
78
|
+
it "creates a plist" do
|
79
|
+
subject
|
80
|
+
|
81
|
+
path = File.join(parent_directory, "#{framework_name}.framework", "Info.plist")
|
82
|
+
|
83
|
+
expected_contents = <<~XML
|
84
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
85
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
86
|
+
<plist version="1.0">
|
87
|
+
<dict>
|
88
|
+
<key>CFBundleName</key>
|
89
|
+
<string>framework_name</string>
|
90
|
+
<key>CFBundleDevelopmentRegion</key>
|
91
|
+
<string>en</string>
|
92
|
+
<key>CFBundleInfoDictionaryVersion</key>
|
93
|
+
<string>6.0</string>
|
94
|
+
<key>CFBundlePackageType</key>
|
95
|
+
<string>FMWK</string>
|
96
|
+
<key>CFBundleIdentifier</key>
|
97
|
+
<string>framework_name.framework_name</string>
|
98
|
+
<key>CFBundleExecutable</key>
|
99
|
+
<string>framework_name</string>
|
100
|
+
<key>CFBundleVersion</key>
|
101
|
+
<string>1</string>
|
102
|
+
</dict>
|
103
|
+
</plist>
|
104
|
+
XML
|
105
|
+
|
106
|
+
expect(File.read(path)).to eq(expected_contents)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
RSpec.describe AppleFrameworks::XCFramework do
|
2
|
+
let(:framework_name) { "framework_name" }
|
3
|
+
let(:parent_directory) { Bundler.root.join("tmp", "framework_name") }
|
4
|
+
let(:framework_paths) do
|
5
|
+
[Bundler.root.join("spec", "fixtures" , "fake.framework")]
|
6
|
+
end
|
7
|
+
let(:described_instance) { described_class.new(framework_name, parent_directory, framework_paths) }
|
8
|
+
|
9
|
+
describe "#build_using_xcode" do
|
10
|
+
subject { described_instance.build_using_xcode }
|
11
|
+
before { FileUtils.rm_rf(parent_directory) }
|
12
|
+
after { FileUtils.rm_rf(parent_directory) }
|
13
|
+
|
14
|
+
context "when there is already a framework in the parent_directory" do
|
15
|
+
before do
|
16
|
+
FileUtils.mkdir_p(File.join(parent_directory, "#{framework_name}.xcframework"))
|
17
|
+
|
18
|
+
expect(File.directory?(parent_directory))
|
19
|
+
.to eq(true)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "raises an error" do
|
23
|
+
expect { subject }
|
24
|
+
.to raise_error("XCFramework already exists")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when there is a file at the target parent_directory" do
|
29
|
+
before do
|
30
|
+
FileUtils.mkdir_p(parent_directory)
|
31
|
+
FileUtils.touch(File.join(parent_directory, "#{framework_name}.xcframework"))
|
32
|
+
|
33
|
+
expect(File.file?(File.join(parent_directory, "#{framework_name}.xcframework")))
|
34
|
+
.to eq(true)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "raises an error" do
|
38
|
+
expect { subject }
|
39
|
+
.to raise_error("File already exists at XCFramework destination")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "when there is an error from xcodebuild" do
|
44
|
+
let(:framework_paths) do
|
45
|
+
[]
|
46
|
+
end
|
47
|
+
|
48
|
+
it "raises an error" do
|
49
|
+
expect { subject }
|
50
|
+
.to raise_error(
|
51
|
+
AppleFrameworks::XCFramework::BuildUsingXcodeFailure,
|
52
|
+
"error: at least one framework or library must be specified."
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context "when there isn't a directory at parent_directory" do
|
58
|
+
before do
|
59
|
+
expect(File.directory?(parent_directory))
|
60
|
+
.to eq(false)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "creates the directory" do
|
64
|
+
subject
|
65
|
+
|
66
|
+
expect(File.directory?(parent_directory))
|
67
|
+
.to eq(true)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "copies the frameworks" do
|
71
|
+
subject
|
72
|
+
|
73
|
+
framework_library_path = Bundler.root.join("spec", "fixtures" , "fake.framework", "fake")
|
74
|
+
xcframework_library_path = File.join(parent_directory, "#{framework_name}.xcframework", "macos-arm64", "fake.framework", "fake")
|
75
|
+
a = Digest::MD5.hexdigest(File.read(framework_library_path))
|
76
|
+
b = Digest::MD5.hexdigest(File.read(xcframework_library_path))
|
77
|
+
|
78
|
+
expect(a).to eq(b)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "creates a plist" do
|
82
|
+
subject
|
83
|
+
|
84
|
+
path = File.join(parent_directory, "#{framework_name}.xcframework", "Info.plist")
|
85
|
+
|
86
|
+
expected_contents = <<~XML
|
87
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
88
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
89
|
+
<plist version="1.0">
|
90
|
+
<dict>
|
91
|
+
<key>AvailableLibraries</key>
|
92
|
+
<array>
|
93
|
+
<dict>
|
94
|
+
<key>LibraryIdentifier</key>
|
95
|
+
<string>macos-arm64</string>
|
96
|
+
<key>LibraryPath</key>
|
97
|
+
<string>fake.framework</string>
|
98
|
+
<key>SupportedArchitectures</key>
|
99
|
+
<array>
|
100
|
+
<string>arm64</string>
|
101
|
+
</array>
|
102
|
+
<key>SupportedPlatform</key>
|
103
|
+
<string>macos</string>
|
104
|
+
</dict>
|
105
|
+
</array>
|
106
|
+
<key>CFBundlePackageType</key>
|
107
|
+
<string>XFWK</string>
|
108
|
+
<key>XCFrameworkFormatVersion</key>
|
109
|
+
<string>1.0</string>
|
110
|
+
</dict>
|
111
|
+
</plist>
|
112
|
+
XML
|
113
|
+
|
114
|
+
expect(File.read(path).gsub("\t", " ")).to eq(expected_contents)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: apple_frameworks
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Inkpen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-10-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
description: Creation of .framework and .xcframework for iOS and/or macOS libraries
|
28
|
+
email:
|
29
|
+
- dan2552@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- lib/apple_frameworks.rb
|
35
|
+
- lib/apple_frameworks/framework.rb
|
36
|
+
- lib/apple_frameworks/version.rb
|
37
|
+
- lib/apple_frameworks/xcframework.rb
|
38
|
+
- spec/framework_spec.rb
|
39
|
+
- spec/spec_helper.rb
|
40
|
+
- spec/xcframework_spec.rb
|
41
|
+
homepage: https://github.com/Dan2552/apple_frameworks
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubygems_version: 3.0.3
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: Creation of .framework and .xcframework for iOS and/or macOS libraries
|
64
|
+
test_files: []
|