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 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
@@ -0,0 +1,5 @@
1
+ # cocoapods-mangle changelog
2
+
3
+ ## 1.0.0 (2017-12-08)
4
+
5
+ * Initial release of cocoapods-mangle with support for mangling source Objective C dependencies.
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,4 @@
1
+ module CocoapodsMangle
2
+ NAME = 'cocoapods-mangle'
3
+ VERSION = '1.0.0'
4
+ 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,2 @@
1
+ require 'cocoapods_mangle/gem_version'
2
+ require 'cocoapods_mangle/hooks'
@@ -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
@@ -0,0 +1,13 @@
1
+ require 'cocoapods'
2
+
3
+ module Pod
4
+ # Overrides logging so it does not pollute the tests
5
+ #
6
+ module UI
7
+ class << self
8
+ def puts(message = '') end
9
+
10
+ def print(message) end
11
+ end
12
+ end
13
+ end
@@ -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