cocoapods-mangle 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/CHANGELOG.md +5 -0
- data/README.md +127 -0
- data/lib/cocoapods_mangle/builder.rb +57 -0
- data/lib/cocoapods_mangle/config.rb +77 -0
- data/lib/cocoapods_mangle/context.rb +84 -0
- data/lib/cocoapods_mangle/defines.rb +114 -0
- data/lib/cocoapods_mangle/gem_version.rb +4 -0
- data/lib/cocoapods_mangle/hooks.rb +15 -0
- data/lib/cocoapods_mangle/post_install.rb +23 -0
- data/lib/cocoapods_mangle.rb +2 -0
- data/lib/cocoapods_plugin.rb +1 -0
- data/spec/integration/integration_spec.rb +95 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/unit/builder_spec.rb +42 -0
- data/spec/unit/config_spec.rb +121 -0
- data/spec/unit/context_spec.rb +129 -0
- data/spec/unit/defines_spec.rb +109 -0
- data/spec/unit/hooks_spec.rb +28 -0
- data/spec/unit/post_install_spec.rb +51 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dc1d2558cf99da5046d0a14bb829edcc388102eb
|
4
|
+
data.tar.gz: a0143366854142cfa87237dcf9d4eb2765f901b8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b0961a1e50462566b277684d752226c52f296198550578976f986543ca5f9d9c6fc901c1a2d504f97f0b6e7ccf7337cab31f06fb130c8f46782bc19c212c4281
|
7
|
+
data.tar.gz: bfd293bdd875576e88774fae3cdb39134e04ae4b46962ee5cc5af1a63e1a8c059b27dbc02a74f61952c7cce6914add6983bf962f9e8486d099d7bd35d3da078d
|
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+

|
2
|
+
|
3
|
+
[](https://www.apache.org/licenses/LICENSE-2.0.html)
|
4
|
+
[](https://circleci.com/gh/intercom/cocoapods-mangle)
|
5
|
+
|
6
|
+
# cocoapods-mangle
|
7
|
+
|
8
|
+
cocoapods-mangle is a CocoaPods plugin which mangles the symbols of your dependencies. Mangling your dependencies' symbols allows more than one copy of a dependency to exist in an app. This is particularly useful for iOS frameworks which do not want to interfere with the host app.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
$ gem install cocoapods-mangle
|
13
|
+
|
14
|
+
## What is mangling?
|
15
|
+
|
16
|
+
Mangling or namespacing your dependencies is a way of ensuring that there are no conflicts between multiple copies of the same dependency in an app. This is most useful when developing third-party frameworks.
|
17
|
+
|
18
|
+
For example, if you are developing a framework `MyFramework.framework` and you include `AFNetworking` as a dependency, all `AFNetworking` classes are included in your framework's binary:
|
19
|
+
|
20
|
+
```
|
21
|
+
➜ nm -gU MyFramework.framework/MyFramework | grep "_OBJC_CLASS_\$.*AF.*"
|
22
|
+
00000000000000e0 S _OBJC_CLASS_$_PodsDummy_AFNetworking
|
23
|
+
00000000000013f0 S _OBJC_CLASS_$_AFNetworkReachabilityManager
|
24
|
+
0000000000001f20 S _OBJC_CLASS_$_AFSecurityPolicy
|
25
|
+
000000000000a938 S _OBJC_CLASS_$_AFHTTPBodyPart
|
26
|
+
000000000000a898 S _OBJC_CLASS_$_AFHTTPRequestSerializer
|
27
|
+
000000000000a9d8 S _OBJC_CLASS_$_AFJSONRequestSerializer
|
28
|
+
000000000000a910 S _OBJC_CLASS_$_AFMultipartBodyStream
|
29
|
+
000000000000aa28 S _OBJC_CLASS_$_AFPropertyListRequestSerializer
|
30
|
+
000000000000a848 S _OBJC_CLASS_$_AFQueryStringPair
|
31
|
+
000000000000a8c0 S _OBJC_CLASS_$_AFStreamingMultipartFormData
|
32
|
+
0000000000004870 S _OBJC_CLASS_$_AFCompoundResponseSerializer
|
33
|
+
00000000000046e0 S _OBJC_CLASS_$_AFHTTPResponseSerializer
|
34
|
+
0000000000004820 S _OBJC_CLASS_$_AFImageResponseSerializer
|
35
|
+
0000000000004730 S _OBJC_CLASS_$_AFJSONResponseSerializer
|
36
|
+
00000000000047d0 S _OBJC_CLASS_$_AFPropertyListResponseSerializer
|
37
|
+
0000000000004780 S _OBJC_CLASS_$_AFXMLParserResponseSerializer
|
38
|
+
```
|
39
|
+
|
40
|
+
This means that if an app includes both `MyFramework.framework` and `AFNetworking`, the app will fail to build with an error that looks something like:
|
41
|
+
|
42
|
+
```
|
43
|
+
ld: 16 duplicate symbols for architecture x86_64
|
44
|
+
clang: error: linker command failed with exit code 1 (use -v to see invocation)
|
45
|
+
```
|
46
|
+
|
47
|
+
However, with mangling enabled through cocoapods-mangle, we can see that the `AFNetworking` classes are now prefixed with `MyFramework_`:
|
48
|
+
|
49
|
+
```
|
50
|
+
➜ nm -gU MyFramework.framework/MyFramework | grep "_OBJC_CLASS_\$.*AF.*"
|
51
|
+
00000000000000e0 S _OBJC_CLASS_$_MyFramework_PodsDummy_AFNetworking
|
52
|
+
00000000000013f0 S _OBJC_CLASS_$_MyFramework_AFNetworkReachabilityManager
|
53
|
+
0000000000001f20 S _OBJC_CLASS_$_MyFramework_AFSecurityPolicy
|
54
|
+
000000000000a938 S _OBJC_CLASS_$_MyFramework_AFHTTPBodyPart
|
55
|
+
000000000000a898 S _OBJC_CLASS_$_MyFramework_AFHTTPRequestSerializer
|
56
|
+
000000000000a9d8 S _OBJC_CLASS_$_MyFramework_AFJSONRequestSerializer
|
57
|
+
000000000000a910 S _OBJC_CLASS_$_MyFramework_AFMultipartBodyStream
|
58
|
+
000000000000aa28 S _OBJC_CLASS_$_MyFramework_AFPropertyListRequestSerializer
|
59
|
+
000000000000a848 S _OBJC_CLASS_$_MyFramework_AFQueryStringPair
|
60
|
+
000000000000a8c0 S _OBJC_CLASS_$_MyFramework_AFStreamingMultipartFormData
|
61
|
+
0000000000004870 S _OBJC_CLASS_$_MyFramework_AFCompoundResponseSerializer
|
62
|
+
00000000000046e0 S _OBJC_CLASS_$_MyFramework_AFHTTPResponseSerializer
|
63
|
+
0000000000004820 S _OBJC_CLASS_$_MyFramework_AFImageResponseSerializer
|
64
|
+
0000000000004730 S _OBJC_CLASS_$_MyFramework_AFJSONResponseSerializer
|
65
|
+
00000000000047d0 S _OBJC_CLASS_$_MyFramework_AFPropertyListResponseSerializer
|
66
|
+
0000000000004780 S _OBJC_CLASS_$_MyFramework_AFXMLParserResponseSerializer
|
67
|
+
```
|
68
|
+
|
69
|
+
The app that includes both `MyFramework.framework` and `AFNetworking` will now build successfully 🎉
|
70
|
+
|
71
|
+
## How it works
|
72
|
+
|
73
|
+
As demonstrated above, `nm` can be used to inspect the symbols such as classes, constants and selectors in a Mach-O binary. When you run `pod install`, cocoapods-mangle builds your dependencies if they have changed, and parses the output of `nm`. It places this output in an `xcconfig` file that looks something like this:
|
74
|
+
|
75
|
+
```
|
76
|
+
MANGLING_DEFINES = PodsDummy_AFNetworking=MyFramework_PodsDummy_AFNetworking AFNetworkReachabilityManager=MyFramework_AFNetworkReachabilityManager AFSecurityPolicy=MyFramework_AFSecurityPolicy AFHTTPBodyPart=MyFramework_AFHTTPBodyPart AFHTTPRequestSerializer=MyFramework_AFHTTPRequestSerializer AFJSONRequestSerializer=MyFramework_AFJSONRequestSerializer AFMultipartBodyStream=MyFramework_AFMultipartBodyStream AFPropertyListRequestSerializer=MyFramework_AFPropertyListRequestSerializer AFQueryStringPair=MyFramework_AFQueryStringPair AFStreamingMultipartFormData=MyFramework_AFStreamingMultipartFormData AFCompoundResponseSerializer=MyFramework_AFCompoundResponseSerializer AFHTTPResponseSerializer=MyFramework_AFHTTPResponseSerializer AFImageResponseSerializer=MyFramework_AFImageResponseSerializer AFJSONResponseSerializer=MyFramework_AFJSONResponseSerializer AFPropertyListResponseSerializer=MyFramework_AFPropertyListResponseSerializer AFXMLParserResponseSerializer=MyFramework_AFXMLParserResponseSerializer
|
77
|
+
|
78
|
+
MANGLED_SPECS_CHECKSUM = 18f61e6e6172fb87ddc7341f3537f30f8c7a3edc
|
79
|
+
```
|
80
|
+
|
81
|
+
This is included in `GCC_PREPROCESSOR_DEFINITIONS` of the `xcconfig` file for every target. All of these symbols will be mangled on subsequent builds.
|
82
|
+
|
83
|
+
The symbols that will be mangled are:
|
84
|
+
|
85
|
+
- Objective C classes. e.g. `AFNetworkReachabilityManager` becomes `MyFramework_AFNetworkReachabilityManager`.
|
86
|
+
- C and Objective C constants. `AFNetworkingReachabilityDidChangeNotification` becomes `MyFramework_AFNetworkingReachabilityDidChangeNotification`.
|
87
|
+
- Objective C category selectors. The first component of the selector is mangled. e.g. `-[NSString xxx_abc:def]` becomes `-[NSString MyFramework_xxx_abc:def]`.
|
88
|
+
|
89
|
+
The plugin has only been fully tested with Objective C dependencies. There is no reason why this could not also work for Swift.
|
90
|
+
|
91
|
+
## Usage
|
92
|
+
|
93
|
+
cocoapods-mangle can be used by adding it to your `Podfile` like this:
|
94
|
+
|
95
|
+
```
|
96
|
+
source 'https://github.com/CocoaPods/Specs.git'
|
97
|
+
|
98
|
+
platform :ios, '8.0'
|
99
|
+
plugin 'cocoapods-mangle'
|
100
|
+
|
101
|
+
target :MyTarget do
|
102
|
+
# Dependencies here
|
103
|
+
end
|
104
|
+
|
105
|
+
```
|
106
|
+
|
107
|
+
Now, each time you run `pod install`, cocoapods-mangle updates the `xcconfig` files for all targets to ensure that all symbols in your dependencies are mangled.
|
108
|
+
|
109
|
+
The plugin can be optionally configured with `:xcconfig_path`, `:mangle_prefix` or `:targets`. Here is an example:
|
110
|
+
|
111
|
+
```
|
112
|
+
plugin 'cocoapods-mangle', targets: ['MyTarget'],
|
113
|
+
mangle_prefix: 'Prefix_'
|
114
|
+
xcconfig_path: 'path/to/mangle.xcconfig'
|
115
|
+
```
|
116
|
+
|
117
|
+
## Caveats
|
118
|
+
|
119
|
+
- cocoapods-mangle will only work for source dependencies. Pre-compiled frameworks cannot be mangled.
|
120
|
+
- Currently only supports iOS. It should be very straightforward to extend support to macOS, tvOS or watchOS.
|
121
|
+
- Category mangling may cause issues if the dependency does not correctly prefix its category selectors (see http://nshipster.com/namespacing/#method-prefixes).
|
122
|
+
|
123
|
+
## Related links
|
124
|
+
|
125
|
+
- [CocoaPods Packager](https://github.com/cocoapods/cocoapods-packager) has similar mangling functionality for packaging `.podspec` files.
|
126
|
+
- http://blog.sigmapoint.pl/avoiding-dependency-collisions-in-ios-static-library-managed-by-cocoapods/
|
127
|
+
- http://pdx.esri.com/blog/namespacing-dependencies/
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'cocoapods'
|
2
|
+
|
3
|
+
module CocoapodsMangle
|
4
|
+
# Builds the supplied targets of a Pods Xcode project.
|
5
|
+
#
|
6
|
+
# This is useful for building pods for mangling purposes
|
7
|
+
class Builder
|
8
|
+
BUILD_DIR = 'build'
|
9
|
+
BUILT_PRODUCTS_DIR = "#{BUILD_DIR}/Release-iphonesimulator"
|
10
|
+
|
11
|
+
# @param [String] pods_project_path
|
12
|
+
# path to the pods project to build.
|
13
|
+
#
|
14
|
+
# @param [Array<String>] pod_target_labels
|
15
|
+
# the pod targets to build.
|
16
|
+
def initialize(pods_project_path, pod_target_labels)
|
17
|
+
@pods_project_path = pods_project_path
|
18
|
+
@pod_target_labels = pod_target_labels
|
19
|
+
end
|
20
|
+
|
21
|
+
# Build the pods project
|
22
|
+
def build!
|
23
|
+
FileUtils.remove_dir(BUILD_DIR, true)
|
24
|
+
@pod_target_labels.each { |target| build_target(target) }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Gives the built binaries to be mangled
|
28
|
+
# @return [Array<String>] Paths to the build pods binaries
|
29
|
+
def binaries_to_mangle
|
30
|
+
static_binaries_to_mangle + dynamic_binaries_to_mangle
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def build_target(target)
|
36
|
+
Pod::UI.message "- Building '#{target}'"
|
37
|
+
output = `xcodebuild -project "#{@pods_project_path}" -target "#{target}" -configuration Release -sdk iphonesimulator build 2>&1`
|
38
|
+
unless $?.success?
|
39
|
+
raise "error: Building the Pods target '#{target}' failed.\ This is the build log:\n#{output}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def static_binaries_to_mangle
|
44
|
+
Dir.glob("#{BUILT_PRODUCTS_DIR}/**/*.a").reject do |binary_path|
|
45
|
+
File.basename(binary_path).start_with?('libPods-')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def dynamic_binaries_to_mangle
|
50
|
+
frameworks = Dir.glob("#{BUILT_PRODUCTS_DIR}/**/*.framework")
|
51
|
+
framework = frameworks.reject do |framework_path|
|
52
|
+
File.basename(framework_path).start_with?('Pods_')
|
53
|
+
end
|
54
|
+
framework.map { |path| "#{path}/#{File.basename(path, '.framework')}" }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'xcodeproj'
|
2
|
+
require 'cocoapods'
|
3
|
+
require 'cocoapods_mangle/builder'
|
4
|
+
require 'cocoapods_mangle/defines'
|
5
|
+
|
6
|
+
module CocoapodsMangle
|
7
|
+
# Manages xcconfig files for configuring mangling.
|
8
|
+
class Config
|
9
|
+
MANGLING_DEFINES_XCCONFIG_KEY = 'MANGLING_DEFINES'
|
10
|
+
MANGLED_SPECS_CHECKSUM_XCCONFIG_KEY = 'MANGLED_SPECS_CHECKSUM'
|
11
|
+
|
12
|
+
# @param [CocoapodsMangle::Context] context The context for mangling.
|
13
|
+
def initialize(context)
|
14
|
+
@context = context
|
15
|
+
end
|
16
|
+
|
17
|
+
# Update the mangling xcconfig file with new mangling defines
|
18
|
+
def update_mangling!
|
19
|
+
Pod::UI.message '- Updating mangling xcconfig' do
|
20
|
+
builder = Builder.new(@context.pods_project_path, @context.pod_target_labels)
|
21
|
+
builder.build!
|
22
|
+
|
23
|
+
defines = Defines.mangling_defines(@context.mangle_prefix, builder.binaries_to_mangle)
|
24
|
+
|
25
|
+
contents = <<~MANGLE_XCCONFIG
|
26
|
+
// This config file is automatically generated any time Podfile.lock changes
|
27
|
+
// Changes should be committed to git along with Podfile.lock
|
28
|
+
|
29
|
+
#{MANGLING_DEFINES_XCCONFIG_KEY} = #{defines.join(' ')}
|
30
|
+
|
31
|
+
// This checksum is used to ensure mangling is up to date
|
32
|
+
#{MANGLED_SPECS_CHECKSUM_XCCONFIG_KEY} = #{@context.specs_checksum}
|
33
|
+
MANGLE_XCCONFIG
|
34
|
+
|
35
|
+
Pod::UI.message "- Writing '#{File.basename(@context.xcconfig_path)}'"
|
36
|
+
File.open(@context.xcconfig_path, 'w') { |xcconfig| xcconfig.write(contents) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Does the mangling xcconfig need to be updated?
|
41
|
+
# @return [Truthy] Does the xcconfig need to be updated?
|
42
|
+
def needs_update?
|
43
|
+
return true unless File.exist?(@context.xcconfig_path)
|
44
|
+
xcconfig_hash = Xcodeproj::Config.new(File.new(@context.xcconfig_path)).to_hash
|
45
|
+
needs_update = xcconfig_hash[MANGLED_SPECS_CHECKSUM_XCCONFIG_KEY] != @context.specs_checksum
|
46
|
+
Pod::UI.message '- Mangling config already up to date' unless needs_update
|
47
|
+
needs_update
|
48
|
+
end
|
49
|
+
|
50
|
+
# Update all pod xcconfigs to use the mangling defines
|
51
|
+
def update_pod_xcconfigs_for_mangling!
|
52
|
+
Pod::UI.message '- Updating Pod xcconfig files' do
|
53
|
+
@context.pod_xcconfig_paths.each do |pod_xcconfig_path|
|
54
|
+
Pod::UI.message "- Updating '#{File.basename(pod_xcconfig_path)}'"
|
55
|
+
update_pod_xcconfig_for_mangling!(pod_xcconfig_path)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Update a mangling config to use the mangling defines
|
61
|
+
# @param [String] pod_xcconfig_path
|
62
|
+
# Path to the pod xcconfig to update
|
63
|
+
def update_pod_xcconfig_for_mangling!(pod_xcconfig_path)
|
64
|
+
mangle_xcconfig_include = "#include \"#{@context.xcconfig_path}\"\n"
|
65
|
+
|
66
|
+
gcc_preprocessor_defs = File.readlines(pod_xcconfig_path).select { |line| line =~ /GCC_PREPROCESSOR_DEFINITIONS/ }.first
|
67
|
+
gcc_preprocessor_defs.strip!
|
68
|
+
|
69
|
+
xcconfig_contents = File.read(pod_xcconfig_path)
|
70
|
+
# import the mangling config
|
71
|
+
new_xcconfig_contents = mangle_xcconfig_include + xcconfig_contents
|
72
|
+
# update GCC_PREPROCESSOR_DEFINITIONS to include mangling
|
73
|
+
new_xcconfig_contents.sub!(gcc_preprocessor_defs, gcc_preprocessor_defs + " $(#{MANGLING_DEFINES_XCCONFIG_KEY})")
|
74
|
+
File.open(pod_xcconfig_path, 'w') { |pod_xcconfig| pod_xcconfig.write(new_xcconfig_contents) }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module CocoapodsMangle
|
2
|
+
# Context for mangling
|
3
|
+
class Context
|
4
|
+
# Initializes the context for mangling
|
5
|
+
# @param [Pod::Installer::PostInstallHooksContext] installer_context
|
6
|
+
# The post install context
|
7
|
+
# @param [Hash] options
|
8
|
+
# @option options [String] :xcconfig_path
|
9
|
+
# The path to the mangling xcconfig
|
10
|
+
# @option options [String] :mangle_prefix
|
11
|
+
# The prefix to prepend to mangled symbols
|
12
|
+
# @option options [Array<String>] :targets
|
13
|
+
# The user targets whose dependencies should be mangled
|
14
|
+
def initialize(installer_context, options)
|
15
|
+
@installer_context = installer_context
|
16
|
+
@options = options
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [String] The path to the mangle xcconfig
|
20
|
+
def xcconfig_path
|
21
|
+
return default_xcconfig_path unless @options[:xcconfig_path]
|
22
|
+
File.join(@installer_context.sandbox.root.parent, @options[:xcconfig_path])
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [String] The mangle prefix to be used
|
26
|
+
def mangle_prefix
|
27
|
+
return default_mangle_prefix unless @options[:mangle_prefix]
|
28
|
+
@options[:mangle_prefix]
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String] The path to pods project
|
32
|
+
def pods_project_path
|
33
|
+
@installer_context.pods_project.path
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [Array<String>] The targets in the pods project to be mangled
|
37
|
+
def pod_target_labels
|
38
|
+
umbrella_pod_targets.map(&:cocoapods_target_label)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Array<String>] Paths to all pod xcconfig files which should be updated
|
42
|
+
def pod_xcconfig_paths
|
43
|
+
pod_xcconfigs = []
|
44
|
+
@installer_context.pods_project.targets.each do |target|
|
45
|
+
target.build_configurations.each do |config|
|
46
|
+
pod_xcconfigs << config.base_configuration_reference.real_path
|
47
|
+
end
|
48
|
+
end
|
49
|
+
pod_xcconfigs.uniq
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String] A checksum representing the current state of the target dependencies
|
53
|
+
def specs_checksum
|
54
|
+
gem_summary = "#{CocoapodsMangle::NAME}=#{CocoapodsMangle::VERSION}"
|
55
|
+
specs = umbrella_pod_targets.map(&:specs).flatten.uniq
|
56
|
+
specs_summary = specs.map(&:checksum).join(',')
|
57
|
+
Digest::SHA1.hexdigest("#{gem_summary},#{specs_summary}")
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def umbrella_pod_targets
|
63
|
+
if @options[:targets].nil? || @options[:targets].empty?
|
64
|
+
return @installer_context.umbrella_targets
|
65
|
+
end
|
66
|
+
@installer_context.umbrella_targets.reject do |target|
|
67
|
+
target_names = target.user_targets.map(&:name)
|
68
|
+
(@options[:targets] & target_names).empty?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def default_xcconfig_path
|
73
|
+
xcconfig_dir = @installer_context.sandbox.target_support_files_root
|
74
|
+
xcconfig_filename = "#{CocoapodsMangle::NAME}.xcconfig"
|
75
|
+
File.join(xcconfig_dir, xcconfig_filename)
|
76
|
+
end
|
77
|
+
|
78
|
+
def default_mangle_prefix
|
79
|
+
project_path = umbrella_pod_targets.first.user_project.path
|
80
|
+
project_name = File.basename(project_path, '.xcodeproj')
|
81
|
+
project_name.tr(' ', '_') + '_'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module CocoapodsMangle
|
2
|
+
# Generates mangling defines from a provided list of binaries
|
3
|
+
module Defines
|
4
|
+
# @param [String] prefix
|
5
|
+
# The prefix to prefix to mangled symbols
|
6
|
+
# @param [Array<String>] binaries_to_mangle
|
7
|
+
# The binaries containing symbols to be mangled
|
8
|
+
# @return [Array<String>] The mangling defines
|
9
|
+
def self.mangling_defines(prefix, binaries_to_mangle)
|
10
|
+
classes = classes(binaries_to_mangle)
|
11
|
+
constants = constants(binaries_to_mangle)
|
12
|
+
category_selectors = category_selectors(binaries_to_mangle, classes)
|
13
|
+
|
14
|
+
defines = prefix_symbols(prefix, classes)
|
15
|
+
defines += prefix_symbols(prefix, constants)
|
16
|
+
defines += prefix_selectors(prefix, category_selectors)
|
17
|
+
defines
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the classes defined in a list of binaries
|
21
|
+
# @param [Array<String>] binaries
|
22
|
+
# The binaries containing symbols to be mangled
|
23
|
+
# @return [Array<String>] The classes defined in the binaries
|
24
|
+
def self.classes(binaries)
|
25
|
+
all_symbols = run_nm(binaries, '-gU')
|
26
|
+
class_symbols = all_symbols.select do |symbol|
|
27
|
+
symbol[/OBJC_CLASS_\$_/]
|
28
|
+
end
|
29
|
+
class_symbols = class_symbols.map { |klass| klass.gsub(/^.*\$_/, '') }
|
30
|
+
class_symbols.uniq
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get the constants defined in a list of binaries
|
34
|
+
# @param [Array<String>] binaries
|
35
|
+
# The binaries containing symbols to be mangled
|
36
|
+
# @return [Array<String>] The constants defined in the binaries
|
37
|
+
def self.constants(binaries)
|
38
|
+
all_symbols = run_nm(binaries, '-gU')
|
39
|
+
consts = all_symbols.select { |const| const[/ S /] }
|
40
|
+
consts = consts.reject { |const| const[/_OBJC_/] }
|
41
|
+
consts = consts.map! { |const| const.gsub(/^.* _/, '') }
|
42
|
+
consts = consts.uniq
|
43
|
+
|
44
|
+
other_consts = all_symbols.select { |const| const[/ T /] }
|
45
|
+
other_consts = other_consts.map! { |const| const.gsub(/^.* _/, '') }
|
46
|
+
other_consts = other_consts.uniq
|
47
|
+
|
48
|
+
consts + other_consts
|
49
|
+
end
|
50
|
+
|
51
|
+
# Get the category selectors defined in a list of binaries
|
52
|
+
# @note Selectors on classes which are being mangled will not be mangled
|
53
|
+
# @param [Array<String>] binaries
|
54
|
+
# The binaries containing symbols to be mangled
|
55
|
+
# @param [Array<String>] classes
|
56
|
+
# The classes which are being mangled
|
57
|
+
# @return [Array<String>] The category selectors defined in the binaries
|
58
|
+
def self.category_selectors(binaries, classes)
|
59
|
+
symbols = run_nm(binaries, '-U')
|
60
|
+
selectors = symbols.select { |selector| selector[/ t [-|+]\[[^ ]*\([^ ]*\) [^ ]*\]/] }
|
61
|
+
selectors = selectors.reject do |selector|
|
62
|
+
class_name = selector[/[-|+]\[(.*?)\(/m, 1]
|
63
|
+
classes.include? class_name
|
64
|
+
end
|
65
|
+
selectors = selectors.map { |selector| selector[/[^ ]*\]\z/][0...-1] }
|
66
|
+
selectors = selectors.map { |selector| selector.split(':').first }
|
67
|
+
selectors.uniq
|
68
|
+
end
|
69
|
+
|
70
|
+
# Prefix a given list of symbols
|
71
|
+
# @param [String] prefix
|
72
|
+
# The prefix to prepend
|
73
|
+
# @param [Array<String>] symbols
|
74
|
+
# The symbols to prefix
|
75
|
+
def self.prefix_symbols(prefix, symbols)
|
76
|
+
symbols.map do |symbol|
|
77
|
+
"#{symbol}=#{prefix}#{symbol}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Prefix a given list of selectors
|
82
|
+
# @param [String] prefix
|
83
|
+
# The prefix to use
|
84
|
+
# @param [Array<String>] selectors
|
85
|
+
# The selectors to prefix
|
86
|
+
def self.prefix_selectors(prefix, selectors)
|
87
|
+
selectors_to_prefix = selectors
|
88
|
+
defines = []
|
89
|
+
|
90
|
+
property_setters = selectors.select { |selector| selector[/\Aset[A-Z]/] }
|
91
|
+
property_setters.each do |property_setter|
|
92
|
+
property_getter = selectors.find do |selector|
|
93
|
+
upper_getter = property_setter[3..-1]
|
94
|
+
lower_getter = upper_getter[0, 1].downcase + upper_getter[1..-1]
|
95
|
+
selector == upper_getter || selector == lower_getter
|
96
|
+
end
|
97
|
+
next if property_getter.nil?
|
98
|
+
|
99
|
+
selectors_to_prefix.reject! { |selector| selector == property_setter }
|
100
|
+
selectors_to_prefix.reject! { |selector| selector == property_getter }
|
101
|
+
|
102
|
+
defines << "#{property_setter}=set#{prefix}#{property_getter}"
|
103
|
+
defines << "#{property_getter}=#{prefix}#{property_getter}"
|
104
|
+
end
|
105
|
+
|
106
|
+
defines += prefix_symbols(prefix, selectors_to_prefix)
|
107
|
+
defines
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.run_nm(binaries, flags)
|
111
|
+
`nm #{flags} #{binaries.join(' ')}`.split("\n")
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'cocoapods_mangle/context'
|
2
|
+
require 'cocoapods_mangle/post_install'
|
3
|
+
|
4
|
+
module CocoapodsMangle
|
5
|
+
# Registers for CocoaPods plugin hooks
|
6
|
+
module Hooks
|
7
|
+
Pod::HooksManager.register(CocoapodsMangle::NAME, :post_install) do |installer_context, options|
|
8
|
+
context = Context.new(installer_context, options)
|
9
|
+
post_install = CocoapodsMangle::PostInstall.new(context)
|
10
|
+
Pod::UI.titled_section 'Updating mangling' do
|
11
|
+
post_install.run!
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'cocoapods'
|
2
|
+
require 'cocoapods_mangle/config'
|
3
|
+
|
4
|
+
module CocoapodsMangle
|
5
|
+
# Runs the post mangling post install action
|
6
|
+
class PostInstall
|
7
|
+
# @param [CocoapodsMangle::Context] context The context for mangling.
|
8
|
+
def initialize(context)
|
9
|
+
@context = context
|
10
|
+
end
|
11
|
+
|
12
|
+
# Run the post install action
|
13
|
+
def run!
|
14
|
+
config.update_mangling! if config.needs_update?
|
15
|
+
config.update_pod_xcconfigs_for_mangling!
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [CocoapodsMangle::Config] The mangling config object
|
19
|
+
def config
|
20
|
+
@config ||= CocoapodsMangle::Config.new(@context)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'cocoapods_mangle'
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'cocoapods_mangle/config'
|
4
|
+
|
5
|
+
def defines_from_xcconfig(xcconfig_path)
|
6
|
+
xcconfig = Xcodeproj::Config.new(File.new(xcconfig_path)).to_hash
|
7
|
+
xcconfig[CocoapodsMangle::Config::MANGLING_DEFINES_XCCONFIG_KEY].split(' ')
|
8
|
+
end
|
9
|
+
|
10
|
+
def build_sample
|
11
|
+
`xcodebuild -workspace "Mangle Integration.xcworkspace" -scheme "Mangle Integration" -sdk iphonesimulator build`
|
12
|
+
$?.success?
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec.shared_examples 'mangling integration' do
|
16
|
+
it 'installs and mangles all expected symbols' do
|
17
|
+
Dir.chdir project_dir
|
18
|
+
|
19
|
+
# Calling the command directly here rather than through the Ruby API
|
20
|
+
# because the CocoaPods Ruby API seems to keep some state between installs
|
21
|
+
`bundle exec pod install`
|
22
|
+
pod_install_success = $?.success?
|
23
|
+
expect(pod_install_success).to be_truthy
|
24
|
+
|
25
|
+
xcconfig_path = File.join(project_dir, "Pods/Target Support Files/#{CocoapodsMangle::NAME}.xcconfig")
|
26
|
+
expect(defines_from_xcconfig(xcconfig_path)).to match_array(expected_defines)
|
27
|
+
expect(build_sample).to be_truthy
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe CocoapodsMangle do
|
32
|
+
let(:tmp_dir) { Dir.mktmpdir }
|
33
|
+
let(:project_dir) { File.join(tmp_dir, 'project') }
|
34
|
+
let(:pod_dir) { File.join(tmp_dir, 'pod') }
|
35
|
+
|
36
|
+
before(:each) do
|
37
|
+
fixtures_dir = File.expand_path("#{File.dirname(__FILE__)}/../fixtures")
|
38
|
+
FileUtils.copy_entry(File.join(fixtures_dir, 'project'), project_dir)
|
39
|
+
FileUtils.copy_entry(File.join(fixtures_dir, 'pod'), pod_dir)
|
40
|
+
File.open(File.join(project_dir, 'Podfile'), 'w') do |podfile|
|
41
|
+
podfile.write(podfile_contents)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'without frameworks' do
|
46
|
+
let(:podfile_contents) do
|
47
|
+
<<~PODFILE
|
48
|
+
platform :ios, '8.0'
|
49
|
+
plugin 'cocoapods-mangle'
|
50
|
+
target 'Mangle Integration' do
|
51
|
+
pod 'ManglePod', path: '../pod'
|
52
|
+
end
|
53
|
+
PODFILE
|
54
|
+
end
|
55
|
+
let(:expected_defines) do
|
56
|
+
%w[
|
57
|
+
PodsDummy_ManglePod=Mangle_Integration_PodsDummy_ManglePod
|
58
|
+
CPMObject=Mangle_Integration_CPMObject
|
59
|
+
CPMConstant=Mangle_Integration_CPMConstant
|
60
|
+
CPMStringFromIntegerFunction=Mangle_Integration_CPMStringFromIntegerFunction
|
61
|
+
cpm_doSomethingWithoutParams=Mangle_Integration_cpm_doSomethingWithoutParams
|
62
|
+
cpm_doSomethingWithParam=Mangle_Integration_cpm_doSomethingWithParam
|
63
|
+
]
|
64
|
+
end
|
65
|
+
|
66
|
+
include_examples 'mangling integration'
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with frameworks' do
|
70
|
+
let(:podfile_contents) do
|
71
|
+
<<~PODFILE
|
72
|
+
platform :ios, '8.0'
|
73
|
+
use_frameworks!
|
74
|
+
plugin 'cocoapods-mangle'
|
75
|
+
target 'Mangle Integration' do
|
76
|
+
pod 'ManglePod', path: '../pod'
|
77
|
+
end
|
78
|
+
PODFILE
|
79
|
+
end
|
80
|
+
let(:expected_defines) do
|
81
|
+
%w[
|
82
|
+
ManglePodVersionNumber=Mangle_Integration_ManglePodVersionNumber
|
83
|
+
ManglePodVersionString=Mangle_Integration_ManglePodVersionString
|
84
|
+
PodsDummy_ManglePod=Mangle_Integration_PodsDummy_ManglePod
|
85
|
+
CPMObject=Mangle_Integration_CPMObject
|
86
|
+
CPMConstant=Mangle_Integration_CPMConstant
|
87
|
+
CPMStringFromIntegerFunction=Mangle_Integration_CPMStringFromIntegerFunction
|
88
|
+
cpm_doSomethingWithoutParams=Mangle_Integration_cpm_doSomethingWithoutParams
|
89
|
+
cpm_doSomethingWithParam=Mangle_Integration_cpm_doSomethingWithParam
|
90
|
+
]
|
91
|
+
end
|
92
|
+
|
93
|
+
include_examples 'mangling integration'
|
94
|
+
end
|
95
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'cocoapods_mangle/builder'
|
3
|
+
|
4
|
+
describe CocoapodsMangle::Builder do
|
5
|
+
let(:pods_project_path) { 'path/to/Pods.xcodeproj' }
|
6
|
+
let(:pod_target_labels) { %w[Pod-A Pod-B] }
|
7
|
+
let(:subject) { CocoapodsMangle::Builder.new(pods_project_path, pod_target_labels) }
|
8
|
+
|
9
|
+
context '.build!' do
|
10
|
+
before do
|
11
|
+
allow(FileUtils).to receive(:remove_dir).with(CocoapodsMangle::Builder::BUILD_DIR, true)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'builds' do
|
15
|
+
expect(FileUtils).to receive(:remove_dir).with(CocoapodsMangle::Builder::BUILD_DIR, true)
|
16
|
+
expect(subject).to receive(:build_target).with('Pod-A')
|
17
|
+
expect(subject).to receive(:build_target).with('Pod-B')
|
18
|
+
subject.build!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context '.binaries_to_mangle' do
|
23
|
+
let(:static_binaries) { %w[path/to/staticA.a path/to/staticB.a] }
|
24
|
+
let(:frameworks) { %w[path/to/FrameworkA.framework path/to/FrameworkB.framework] }
|
25
|
+
let(:framework_binaries) do
|
26
|
+
frameworks.map { |path| "#{path}/#{File.basename(path, '.framework')}" }
|
27
|
+
end
|
28
|
+
before do
|
29
|
+
allow(Dir).to receive(:glob)
|
30
|
+
.with("#{CocoapodsMangle::Builder::BUILT_PRODUCTS_DIR}/**/*.a")
|
31
|
+
.and_return(static_binaries + ['path/to/libPods-A.a'])
|
32
|
+
allow(Dir).to receive(:glob)
|
33
|
+
.with("#{CocoapodsMangle::Builder::BUILT_PRODUCTS_DIR}/**/*.framework")
|
34
|
+
.and_return(frameworks + ['path/to/Pods_A.framework'])
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'gives the static and framework binaries' do
|
38
|
+
binaries = static_binaries + framework_binaries
|
39
|
+
expect(subject.binaries_to_mangle).to match_array(binaries)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'cocoapods_mangle/config'
|
3
|
+
|
4
|
+
describe CocoapodsMangle::Config do
|
5
|
+
let(:context) do
|
6
|
+
instance_double('Mangle context',
|
7
|
+
xcconfig_path: 'path/to/mangle.xcconfig',
|
8
|
+
pod_target_labels: ['Pods-A'],
|
9
|
+
pods_project_path: 'path/to/Pods.xcodeproj',
|
10
|
+
mangle_prefix: 'prefix_',
|
11
|
+
specs_checksum: 'checksum',
|
12
|
+
pod_xcconfig_paths: ['path/to/A.xcconfig', 'path/to/B.xcconfig'])
|
13
|
+
end
|
14
|
+
let(:subject) do
|
15
|
+
CocoapodsMangle::Config.new(context)
|
16
|
+
end
|
17
|
+
|
18
|
+
context '.update_mangling!' do
|
19
|
+
let(:xcconfig_file) { double('config') }
|
20
|
+
let(:binaries_to_mangle) { ['binary_A', 'binary_B'] }
|
21
|
+
let(:mangling_defines) { ['A=B', 'C=D'] }
|
22
|
+
let(:builder) { double('builder') }
|
23
|
+
|
24
|
+
before do
|
25
|
+
allow(CocoapodsMangle::Builder).to receive(:new).with(context.pods_project_path, context.pod_target_labels).and_return(builder)
|
26
|
+
allow(builder).to receive(:build!)
|
27
|
+
allow(builder).to receive(:binaries_to_mangle).and_return(binaries_to_mangle)
|
28
|
+
allow(CocoapodsMangle::Defines).to receive(:mangling_defines).with(context.mangle_prefix, binaries_to_mangle).and_return(mangling_defines)
|
29
|
+
allow(File).to receive(:open).and_call_original
|
30
|
+
allow(File).to receive(:open).with(context.xcconfig_path, 'w').and_yield(xcconfig_file)
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'updates mangling' do
|
34
|
+
expect(builder).to receive(:build!)
|
35
|
+
xcconfig_contents = ''
|
36
|
+
expect(xcconfig_file).to receive(:write) { |arg| xcconfig_contents = arg }
|
37
|
+
|
38
|
+
subject.update_mangling!
|
39
|
+
|
40
|
+
expect(xcconfig_contents).to include("MANGLING_DEFINES = #{mangling_defines.join(" ")}")
|
41
|
+
expect(xcconfig_contents).to include('MANGLED_SPECS_CHECKSUM = checksum')
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context '.needs_update?' do
|
46
|
+
let(:xcconfig_file) { double('config file') }
|
47
|
+
let(:xcodeproj_config) { double('xcodeproj config') }
|
48
|
+
let(:xcconfig_specs_checksum) { 'checksum' }
|
49
|
+
|
50
|
+
before do
|
51
|
+
allow(File).to receive(:exist?).and_call_original
|
52
|
+
allow(File).to receive(:exist?).with(context.xcconfig_path).and_return(true)
|
53
|
+
allow(File).to receive(:new).and_call_original
|
54
|
+
allow(File).to receive(:new).with(context.xcconfig_path).and_return(xcconfig_file)
|
55
|
+
allow(Xcodeproj::Config).to receive(:new).with(xcconfig_file).and_return(xcodeproj_config)
|
56
|
+
allow(xcodeproj_config).to receive(:to_hash).and_return('MANGLING_DEFINES' => 'A=B C=D',
|
57
|
+
'MANGLED_SPECS_CHECKSUM' => "#{xcconfig_specs_checksum}")
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'equal checksums' do
|
61
|
+
before do
|
62
|
+
allow(context).to receive(:specs_checksum).and_return('checksum')
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'does not need an update' do
|
66
|
+
expect(subject.needs_update?).to eq(false)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'different checksums' do
|
71
|
+
before do
|
72
|
+
allow(context).to receive(:specs_checksum).and_return('other_checksum')
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'needs an update' do
|
76
|
+
expect(subject.needs_update?).to eq(true)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'no config' do
|
81
|
+
before do
|
82
|
+
allow(File).to receive(:exist?).with(context.xcconfig_path).and_return(false)
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'needs an update' do
|
86
|
+
expect(subject.needs_update?).to eq(true)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context '.update_pod_xcconfigs_for_mangling!' do
|
92
|
+
it 'updates each unique config' do
|
93
|
+
context.pod_xcconfig_paths.each do |pod_xcconfig|
|
94
|
+
expect(subject).to receive(:update_pod_xcconfig_for_mangling!).with(pod_xcconfig)
|
95
|
+
end
|
96
|
+
subject.update_pod_xcconfigs_for_mangling!
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context '.update_pod_xcconfig_for_mangling!' do
|
101
|
+
let(:pod_xcconfig_path) { '/path/to/pod.xcconfig' }
|
102
|
+
let(:pod_xcconfig_content) { "GCC_PREPROCESSOR_DEFINITIONS = A=B\nKEY = VALUE" }
|
103
|
+
let(:pod_xcconfig_file) { double('pod_config') }
|
104
|
+
|
105
|
+
before do
|
106
|
+
allow(File).to receive(:readlines).and_call_original
|
107
|
+
allow(File).to receive(:readlines).and_return(pod_xcconfig_content.split("\n"))
|
108
|
+
allow(File).to receive(:read).and_call_original
|
109
|
+
allow(File).to receive(:read).and_return(pod_xcconfig_content)
|
110
|
+
allow(File).to receive(:open).and_call_original
|
111
|
+
allow(File).to receive(:open).with(pod_xcconfig_path, 'w').and_yield(pod_xcconfig_file)
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'updates the pod config' do
|
115
|
+
pod_xcconfig_contents = ''
|
116
|
+
expect(pod_xcconfig_file).to receive(:write) { |arg| pod_xcconfig_contents = arg }
|
117
|
+
subject.update_pod_xcconfig_for_mangling!(pod_xcconfig_path)
|
118
|
+
expect(pod_xcconfig_contents).to eq("#include \"#{context.xcconfig_path}\"\nGCC_PREPROCESSOR_DEFINITIONS = A=B $(MANGLING_DEFINES)\nKEY = VALUE")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'cocoapods_mangle/context'
|
3
|
+
|
4
|
+
describe CocoapodsMangle::Context do
|
5
|
+
let(:umbrella_targets) do
|
6
|
+
[
|
7
|
+
instance_double('umbrella target A', cocoapods_target_label: 'Pods-A', user_targets: [instance_double('target A', name: 'A')]),
|
8
|
+
instance_double('umbrella target B', cocoapods_target_label: 'Pods-B', user_targets: [instance_double('target B', name: 'B')]),
|
9
|
+
instance_double('umbrella target C', cocoapods_target_label: 'Pods-C', user_targets: [instance_double('target C', name: 'C')])
|
10
|
+
]
|
11
|
+
end
|
12
|
+
let(:installer_context) { instance_double('installer context', umbrella_targets: umbrella_targets) }
|
13
|
+
let(:options) { {} }
|
14
|
+
let(:subject) { CocoapodsMangle::Context.new(installer_context, options) }
|
15
|
+
|
16
|
+
context '.xcconfig_path' do
|
17
|
+
before do
|
18
|
+
allow(installer_context).to receive_message_chain(:sandbox, :target_support_files_root).and_return( Pathname.new('/support_files') )
|
19
|
+
allow(installer_context).to receive_message_chain(:sandbox, :root, :parent).and_return( Pathname.new('/parent') )
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'No options' do
|
23
|
+
it 'gives the default xcconfig path' do
|
24
|
+
expect(subject.xcconfig_path).to eq("/support_files/#{CocoapodsMangle::NAME}.xcconfig")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'User provided xcconfig path' do
|
29
|
+
let(:options) { { xcconfig_path: 'path/to/mangle.xcconfig' } }
|
30
|
+
it 'gives the user xcconfig path, relative to the project' do
|
31
|
+
expect(subject.xcconfig_path).to eq("/parent/#{options[:xcconfig_path]}")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context '.mangle_prefix' do
|
37
|
+
context 'No options' do
|
38
|
+
before do
|
39
|
+
allow(umbrella_targets.first).to receive_message_chain(:user_project, :path).and_return('path/to/Project.xcodeproj')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'gives the project name as the prefix' do
|
43
|
+
expect(subject.mangle_prefix).to eq('Project_')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'No options with space in project name' do
|
48
|
+
before do
|
49
|
+
allow(umbrella_targets.first).to receive_message_chain(:user_project, :path).and_return('path/to/Project Name.xcodeproj')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'gives the project name with underscores as the prefix' do
|
53
|
+
expect(subject.mangle_prefix).to eq('Project_Name_')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'User provided prefix' do
|
58
|
+
let(:options) { { mangle_prefix: 'Prefix_' } }
|
59
|
+
|
60
|
+
it 'gives the user prefix' do
|
61
|
+
expect(subject.mangle_prefix).to eq(options[:mangle_prefix])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context '.pods_project_path' do
|
67
|
+
let(:pods_project) { instance_double('pods project', path: 'path/to/Pods.xcodeproj') }
|
68
|
+
|
69
|
+
before do
|
70
|
+
allow(installer_context).to receive(:pods_project).and_return(pods_project)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'gives the project path' do
|
74
|
+
expect(subject.pods_project_path).to eq(pods_project.path)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context '.pod_target_labels' do
|
79
|
+
context 'No options' do
|
80
|
+
it 'gives all targets' do
|
81
|
+
expect(subject.pod_target_labels).to eq(['Pods-A', 'Pods-B', 'Pods-C'])
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'With targets' do
|
86
|
+
let(:options) { { targets: ['A', 'B'] } }
|
87
|
+
it 'gives only requested targets' do
|
88
|
+
expect(subject.pod_target_labels).to eq(['Pods-A', 'Pods-B'])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context '.pod_xcconfig_paths' do
|
94
|
+
let(:pods_project) { instance_double('pods project', path: 'path/to/Pods.xcodeproj') }
|
95
|
+
let(:pod_target) { double('target') }
|
96
|
+
let(:debug_build_configuration) { double('debug') }
|
97
|
+
let(:release_build_configuration) { double('release') }
|
98
|
+
|
99
|
+
before do
|
100
|
+
allow(installer_context).to receive(:pods_project).and_return(pods_project)
|
101
|
+
allow(pods_project).to receive(:targets).and_return([pod_target])
|
102
|
+
build_configurations = [debug_build_configuration, release_build_configuration]
|
103
|
+
allow(pod_target).to receive(:build_configurations).and_return(build_configurations)
|
104
|
+
build_configurations.each do |config|
|
105
|
+
allow(config).to receive_message_chain(:base_configuration_reference, :real_path).and_return('path/to/pod.xcconfig')
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'gives the pod xcconfigs' do
|
110
|
+
expect(subject.pod_xcconfig_paths).to eq(['path/to/pod.xcconfig'])
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context '.specs_checksum' do
|
115
|
+
let(:gem_summary) { "#{CocoapodsMangle::NAME}=#{CocoapodsMangle::VERSION}" }
|
116
|
+
let(:spec_A) { instance_double('Spec A', checksum: 'checksum_A') }
|
117
|
+
let(:spec_B) { instance_double('Spec B', checksum: 'checksum_B') }
|
118
|
+
let(:options) { { targets: ['A'] } }
|
119
|
+
|
120
|
+
before do
|
121
|
+
allow(umbrella_targets.first).to receive(:specs).and_return([spec_A, spec_B])
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'gives the checksum' do
|
125
|
+
summary = "#{gem_summary},checksum_A,checksum_B"
|
126
|
+
expect(subject.specs_checksum).to eq(Digest::SHA1.hexdigest(summary))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'cocoapods_mangle/defines'
|
3
|
+
|
4
|
+
describe CocoapodsMangle::Defines do
|
5
|
+
let(:non_global_defined_symbols) do
|
6
|
+
File.read("#{File.dirname(__FILE__)}/../fixtures/symbols/non_global_defined_symbols.txt").split("\n")
|
7
|
+
end
|
8
|
+
let(:all_defined_symbols) do
|
9
|
+
File.read("#{File.dirname(__FILE__)}/../fixtures/symbols/all_defined_symbols.txt").split("\n")
|
10
|
+
end
|
11
|
+
let(:defines) { CocoapodsMangle::Defines.mangling_defines('Prefix_', ['A.a', 'B.a']) }
|
12
|
+
|
13
|
+
before do
|
14
|
+
allow(CocoapodsMangle::Defines).to receive(:run_nm).with(['A.a', 'B.a'], '-gU').and_return(non_global_defined_symbols)
|
15
|
+
allow(CocoapodsMangle::Defines).to receive(:run_nm).with(['A.a', 'B.a'], '-U').and_return(all_defined_symbols)
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'Class mangling' do
|
19
|
+
let(:expected_classes) do
|
20
|
+
%w[
|
21
|
+
PINDataTaskOperation
|
22
|
+
PINProgressiveImage
|
23
|
+
PodsDummy_PINRemoteImage
|
24
|
+
PINRemoteImageCallbacks
|
25
|
+
PINRemoteImageCategoryManager
|
26
|
+
PINRemoteImageDownloadTask
|
27
|
+
PINRemoteImageManager
|
28
|
+
PINTaskQOS
|
29
|
+
PINRemoteImageManagerResult
|
30
|
+
PINRemoteImageProcessorTask
|
31
|
+
PINRemoteImageTask
|
32
|
+
PINRemoteLock
|
33
|
+
PINURLSessionManager
|
34
|
+
FLAnimatedImage
|
35
|
+
FLWeakProxy
|
36
|
+
FLAnimatedImageView
|
37
|
+
]
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should mangle the classes' do
|
41
|
+
expected_classes.each do |class_name|
|
42
|
+
expect(defines).to include("#{class_name}=Prefix_#{class_name}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'Constant mangling' do
|
48
|
+
let(:expected_constants) do
|
49
|
+
%w[
|
50
|
+
PINRemoteImageManagerErrorDomain
|
51
|
+
PINImageJPEGRepresentation
|
52
|
+
PINImagePNGRepresentation
|
53
|
+
pin_UIImageOrientationFromImageSource
|
54
|
+
dataTaskPriorityWithImageManagerPriority
|
55
|
+
operationPriorityWithImageManagerPriority
|
56
|
+
kFLAnimatedImageDelayTimeIntervalMinimum
|
57
|
+
]
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should mangle the constants' do
|
61
|
+
expected_constants.each do |const|
|
62
|
+
expect(defines).to include("#{const}=Prefix_#{const}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'Category selector mangling' do
|
68
|
+
let(:expected_non_property_selectors) do
|
69
|
+
%w[
|
70
|
+
pin_cancelImageDownload
|
71
|
+
pin_clearImages
|
72
|
+
pin_downloadImageOperationUUID
|
73
|
+
pin_ignoreGIFs
|
74
|
+
pin_setDownloadImageOperationUUID
|
75
|
+
pin_setImageFromURL
|
76
|
+
pin_setImageFromURLs
|
77
|
+
pin_setPlaceholderWithImage
|
78
|
+
pin_updateUIWithImage
|
79
|
+
pin_isGIF
|
80
|
+
pin_decodedImageWithCGImageRef
|
81
|
+
pin_decodedImageWithData
|
82
|
+
pin_addOperationWithQueuePriority
|
83
|
+
]
|
84
|
+
end
|
85
|
+
let(:mangled_class_non_property_selectors) do
|
86
|
+
%w[
|
87
|
+
logStringFromBlock
|
88
|
+
setLogBlock
|
89
|
+
]
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'should mangle the category selectors' do
|
93
|
+
expected_non_property_selectors.each do |sel|
|
94
|
+
expect(defines).to include("#{sel}=Prefix_#{sel}")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'should not mangle the category selectors on mangled classes' do
|
99
|
+
mangled_class_non_property_selectors.each do |sel|
|
100
|
+
expect(defines).not_to include("#{sel}=Prefix_#{sel}")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should mangle the category property selectors' do
|
105
|
+
expect(defines).to include('pin_updateWithProgress=Prefix_pin_updateWithProgress')
|
106
|
+
expect(defines).to include('setPin_updateWithProgress=setPrefix_pin_updateWithProgress')
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'cocoapods_mangle/hooks'
|
3
|
+
|
4
|
+
def trigger_post_install(installer_context, options)
|
5
|
+
post_install_hooks = Pod::HooksManager.registrations[:post_install]
|
6
|
+
hook = post_install_hooks.find { |h| h.plugin_name == CocoapodsMangle::NAME }
|
7
|
+
hook.block.call(installer_context, options)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe CocoapodsMangle::Hooks do
|
11
|
+
let(:installer_context) { instance_double('installer context') }
|
12
|
+
let(:options) { double('options') }
|
13
|
+
let(:mangle_context) { instance_double('installer context') }
|
14
|
+
let(:post_install) { double('post install') }
|
15
|
+
|
16
|
+
before do
|
17
|
+
allow(CocoapodsMangle::Context).to receive(:new).with(installer_context, options).and_return(mangle_context)
|
18
|
+
allow(CocoapodsMangle::PostInstall).to receive(:new).with(mangle_context).and_return(post_install)
|
19
|
+
allow(post_install).to receive(:run!)
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'post install' do
|
23
|
+
it 'runs the post install action' do
|
24
|
+
expect(post_install).to receive(:run!)
|
25
|
+
trigger_post_install(installer_context, options)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'cocoapods_mangle/post_install'
|
3
|
+
|
4
|
+
describe CocoapodsMangle::PostInstall do
|
5
|
+
let(:context) do
|
6
|
+
instance_double('Mangle context')
|
7
|
+
end
|
8
|
+
let(:subject) do
|
9
|
+
CocoapodsMangle::PostInstall.new(context)
|
10
|
+
end
|
11
|
+
let(:config) { double('config') }
|
12
|
+
|
13
|
+
context '.run!' do
|
14
|
+
let(:specs_checksum) { 'checksum' }
|
15
|
+
|
16
|
+
before do
|
17
|
+
allow(subject).to receive(:config).and_return(config)
|
18
|
+
allow(context).to receive(:specs_checksum).and_return(specs_checksum)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'updates mangling and pod xcconfigs' do
|
22
|
+
allow(config).to receive(:needs_update?).and_return(true)
|
23
|
+
expect(config).to receive(:update_mangling!)
|
24
|
+
expect(config).to receive(:update_pod_xcconfigs_for_mangling!)
|
25
|
+
|
26
|
+
subject.run!
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'updates pod xcconfigs only' do
|
30
|
+
allow(config).to receive(:needs_update?).and_return(false)
|
31
|
+
expect(config).not_to receive(:update_mangling!)
|
32
|
+
expect(config).to receive(:update_pod_xcconfigs_for_mangling!)
|
33
|
+
|
34
|
+
subject.run!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context '.config' do
|
39
|
+
let(:specs_checksum) { 'checksum' }
|
40
|
+
|
41
|
+
before do
|
42
|
+
allow(subject).to receive(:specs_checksum).and_return(specs_checksum)
|
43
|
+
allow(CocoapodsMangle::Config).to receive(:new).with(context).and_return(config)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'creates a config' do
|
47
|
+
expect(CocoapodsMangle::Config).to receive(:new).with(context)
|
48
|
+
expect(subject.config).to eq(config)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cocoapods-mangle
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Treanor
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-12-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: cocoapods
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
description: Mangling your dependencies symbols allows more than one copy of a dependency
|
28
|
+
to exist without errors. This plugin mangles your dependecies to make this possible
|
29
|
+
email:
|
30
|
+
- james@intercom.io
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files:
|
34
|
+
- README.md
|
35
|
+
- CHANGELOG.md
|
36
|
+
files:
|
37
|
+
- CHANGELOG.md
|
38
|
+
- README.md
|
39
|
+
- lib/cocoapods_mangle.rb
|
40
|
+
- lib/cocoapods_mangle/builder.rb
|
41
|
+
- lib/cocoapods_mangle/config.rb
|
42
|
+
- lib/cocoapods_mangle/context.rb
|
43
|
+
- lib/cocoapods_mangle/defines.rb
|
44
|
+
- lib/cocoapods_mangle/gem_version.rb
|
45
|
+
- lib/cocoapods_mangle/hooks.rb
|
46
|
+
- lib/cocoapods_mangle/post_install.rb
|
47
|
+
- lib/cocoapods_plugin.rb
|
48
|
+
- spec/integration/integration_spec.rb
|
49
|
+
- spec/spec_helper.rb
|
50
|
+
- spec/unit/builder_spec.rb
|
51
|
+
- spec/unit/config_spec.rb
|
52
|
+
- spec/unit/context_spec.rb
|
53
|
+
- spec/unit/defines_spec.rb
|
54
|
+
- spec/unit/hooks_spec.rb
|
55
|
+
- spec/unit/post_install_spec.rb
|
56
|
+
homepage: https://github.com/intercom/cocoapods-mangle
|
57
|
+
licenses:
|
58
|
+
- Apache-2.0
|
59
|
+
metadata: {}
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.5.2.1
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: A CocoaPods plugin which mangles the symbols of your dependencies
|
80
|
+
test_files:
|
81
|
+
- spec/integration/integration_spec.rb
|
82
|
+
- spec/spec_helper.rb
|
83
|
+
- spec/unit/builder_spec.rb
|
84
|
+
- spec/unit/config_spec.rb
|
85
|
+
- spec/unit/context_spec.rb
|
86
|
+
- spec/unit/defines_spec.rb
|
87
|
+
- spec/unit/hooks_spec.rb
|
88
|
+
- spec/unit/post_install_spec.rb
|