cocoapods-mangle 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/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
|
+
![Intercom](Intercom_logo-github.png)
|
2
|
+
|
3
|
+
[![Apache License](http://img.shields.io/badge/license-APACHE2-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0.html)
|
4
|
+
[![CircleCI](https://circleci.com/gh/intercom/cocoapods-mangle.svg?style=svg)](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
|