apple_frameworks 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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,3 @@
1
+ module AppleFrameworks
2
+ VERSION = "1.0.0"
3
+ 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,6 @@
1
+ require "digest"
2
+ require "tempfile"
3
+
4
+ require "apple_frameworks/version"
5
+ require "apple_frameworks/framework"
6
+ require "apple_frameworks/xcframework"
@@ -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
@@ -0,0 +1,7 @@
1
+ require "apple_frameworks"
2
+
3
+ RSpec.configure do |config|
4
+ config.color = true
5
+ config.tty = true
6
+ config.filter_run_when_matching :focus
7
+ end
@@ -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: []