cocoapods-mangle 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ 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