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 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: []