apple_frameworks 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|