Skip to content

Commit 61a1e0d

Browse files
authored
Rewrite the plugin to monkey-patch cocoapods installer methods (#3)
* Add specs * Rewrite the plugin to monkey-patch cocoapods installer methods This allows us to avoid needing to re-write the user project on every single pod install, which will save multiple seconds * Fix rubocop violations * Manually install bundler on Travis
1 parent a0e97a4 commit 61a1e0d

File tree

6 files changed

+240
-112
lines changed

6 files changed

+240
-112
lines changed

.rubocop_todo.yml

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,48 @@
11
# This configuration was generated by
22
# `rubocop --auto-gen-config`
3-
# on 2018-02-14 14:02:09 -0800 using RuboCop version 0.52.1.
3+
# on 2018-03-29 14:25:48 -0400 using RuboCop version 0.49.1.
44
# The point is for the user to remove these configuration records
55
# one by one as the offenses are removed from the code base.
66
# Note that changes in the inspected code, or installation of new
77
# versions of RuboCop, may require this file to be generated again.
88

9-
# Offense count: 2
9+
# Offense count: 1
1010
# Cop supports --auto-correct.
11-
# Configuration parameters: EnforcedStyle.
11+
# Configuration parameters: EnforcedStyle, SupportedStyles.
1212
# SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent
1313
Layout/IndentHeredoc:
1414
Exclude:
1515
- 'lib/cocoapods_amicable.rb'
1616

17-
# Offense count: 2
17+
# Offense count: 3
1818
# Configuration parameters: AllowSafeAssignment.
1919
Lint/AssignmentInCondition:
2020
Exclude:
2121
- 'lib/cocoapods_amicable.rb'
2222

23-
# Offense count: 3
24-
# Configuration parameters: CountComments.
25-
Metrics/MethodLength:
26-
Max: 22
23+
# Offense count: 2
24+
Metrics/AbcSize:
25+
Max: 24
2726

28-
# Offense count: 1
29-
# Configuration parameters: Blacklist.
30-
# Blacklist: END, (?-mix:EO[A-Z]{1})
31-
Naming/HeredocDelimiterNaming:
32-
Exclude:
33-
- 'lib/cocoapods_amicable.rb'
27+
# Offense count: 5
28+
# Configuration parameters: CountComments, ExcludedMethods.
29+
Metrics/BlockLength:
30+
Max: 89
31+
32+
# Offense count: 16
33+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
34+
# URISchemes: http, https
35+
Metrics/LineLength:
36+
Max: 205
3437

3538
# Offense count: 1
39+
# Configuration parameters: CountComments.
40+
Metrics/MethodLength:
41+
Max: 30
42+
43+
# Offense count: 6
3644
Style/Documentation:
3745
Exclude:
3846
- 'spec/**/*'
3947
- 'test/**/*'
4048
- 'lib/cocoapods_amicable.rb'
41-
42-
# Offense count: 1
43-
# Configuration parameters: MinBodyLength.
44-
Style/GuardClause:
45-
Exclude:
46-
- 'lib/cocoapods_amicable.rb'
47-
48-
# Offense count: 5
49-
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
50-
# URISchemes: http, https
51-
Metrics/LineLength:
52-
Max: 112

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ branches:
22
only:
33
- master
44

5+
before_script:
6+
- gem install bundler
7+
58
language: ruby
69
cache: bundler
710
rvm:
811
- 2.0.0-p647
912
- 2.3.4
1013
- 2.4.1
14+
- 2.5.1

lib/cocoapods_amicable.rb

Lines changed: 59 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,17 @@
11
# frozen_string_literal: true
22

33
module CocoaPodsAmicable
4-
class PodfileChecksumFixer
5-
def initialize(post_install_context)
6-
@post_install_context = post_install_context
7-
end
8-
9-
def fix!
10-
Pod::UI.titled_section 'Moving the Podfile checksum from the lockfile' do
11-
@checksum = remove_checksum_from_lockfiles
12-
write_sha1_file
13-
update_check_manifest_script_phases
14-
end
15-
end
16-
17-
private
18-
19-
attr_reader :checksum
20-
21-
def sandbox
22-
@post_install_context.sandbox
23-
end
24-
25-
def lockfiles
26-
[Pod::Config.instance.lockfile_path, sandbox.manifest_path].map do |lockfile_path|
27-
Pod::Lockfile.from_file(lockfile_path)
28-
end
29-
end
30-
31-
def remove_checksum_from_lockfiles
32-
checksums = lockfiles.map do |lockfile|
33-
checksum = lockfile.internal_data.delete('PODFILE CHECKSUM')
34-
lockfile.write_to_disk(lockfile.defined_in_file)
35-
checksum
36-
end.uniq
37-
case checksums.size
38-
when 1
39-
checksums.first
40-
else
41-
raise 'Multiple (different) podfiles checksums found'
42-
end
43-
end
44-
45-
def sha1_file_path
46-
sandbox.root + 'Podfile.sha1'
47-
end
48-
49-
def podfile_basename
50-
File.basename(Pod::Config.instance.podfile.defined_in_file)
51-
end
52-
53-
def write_sha1_file
54-
return unless name = podfile_basename
55-
sha1_file_path.open('w') do |f|
56-
f.write <<-EOS
57-
#{checksum} #{name}
58-
EOS
59-
end
60-
end
4+
module TargetIntegratorMixin
5+
def add_check_manifest_lock_script_phase
6+
podfile = target.target_definition.podfile
7+
return super unless podfile.plugins.key?('cocoapods-amicable')
618

62-
def update_check_manifest_script_phases
63-
user_projects = []
64-
@post_install_context.umbrella_targets.each do |umbrella_target|
65-
user_projects << umbrella_target.user_project
66-
umbrella_target.user_targets.each do |user_target|
67-
build_phase = user_target.build_phases.find do |bp|
68-
bp.name.end_with? Pod::Installer::UserProjectIntegrator::TargetIntegrator::CHECK_MANIFEST_PHASE_NAME
69-
end
70-
update_check_manifest_script_phase(build_phase)
71-
end
72-
end
73-
74-
user_projects.uniq.each(&:save)
75-
end
9+
phase_name = Pod::Installer::UserProjectIntegrator::TargetIntegrator::CHECK_MANIFEST_PHASE_NAME
10+
native_targets.each do |native_target|
11+
phase = Pod::Installer::UserProjectIntegrator::TargetIntegrator.create_or_update_build_phase(native_target, Pod::Installer::UserProjectIntegrator::TargetIntegrator::BUILD_PHASE_PREFIX + phase_name)
12+
native_target.build_phases.unshift(phase).uniq! unless native_target.build_phases.first == phase
7613

77-
def update_check_manifest_script_phase(build_phase)
78-
build_phase.shell_script = <<-SH
14+
phase.shell_script = <<-SH
7915
set -e
8016
set -u
8117
set -o pipefail
@@ -94,16 +30,58 @@ def update_check_manifest_script_phase(build_phase)
9430
9531
# This output is used by Xcode 'outputs' to avoid re-running this script phase.
9632
echo "SUCCESS" > "${SCRIPT_OUTPUT_FILE_0}"
97-
SH
33+
SH
34+
35+
phase.input_paths = %w[
36+
${PODS_PODFILE_DIR_PATH}/Podfile.lock
37+
${PODS_ROOT}/Manifest.lock
38+
${PODS_ROOT}/Podfile.sha1
39+
]
40+
if name = podfile.defined_in_file && podfile.defined_in_file.basename
41+
phase.input_paths << "${PODS_PODFILE_DIR_PATH}/#{name}"
42+
end
43+
phase.output_paths = [target.check_manifest_lock_script_output_file_path]
44+
end
45+
end
46+
end
47+
48+
module InstallerMixin
49+
def write_lockfiles
50+
super
51+
return unless podfile.plugins.key?('cocoapods-amicable')
52+
return unless checksum = podfile.checksum
53+
return unless podfile_path = podfile.defined_in_file
54+
checksum_path = sandbox.root + 'Podfile.sha1'
9855

99-
build_phase.input_paths = %w[
100-
${PODS_PODFILE_DIR_PATH}/Podfile.lock
101-
${PODS_ROOT}/Manifest.lock
102-
${PODS_ROOT}/Podfile.sha1
103-
]
104-
if name = podfile_basename
105-
build_phase.input_paths << "${PODS_PODFILE_DIR_PATH}/#{name}"
56+
Pod::UI.message "- Writing Podfile checksum in #{Pod::UI.path checksum_path}" do
57+
checksum_path.open('w') { |f| f << checksum << ' ' << podfile_path.basename.to_s << "\n" }
10658
end
10759
end
10860
end
61+
62+
module LockfileMixin
63+
def generate(podfile, *)
64+
lockfile = super
65+
lockfile.internal_data.delete('PODFILE CHECKSUM') if podfile.plugins.key?('cocoapods-amicable')
66+
lockfile
67+
end
68+
end
69+
end
70+
71+
module Pod
72+
class Installer
73+
prepend ::CocoaPodsAmicable::InstallerMixin
74+
75+
class UserProjectIntegrator
76+
class TargetIntegrator
77+
prepend ::CocoaPodsAmicable::TargetIntegratorMixin
78+
end
79+
end
80+
end
81+
82+
class Lockfile
83+
class << self
84+
prepend ::CocoaPodsAmicable::LockfileMixin
85+
end
86+
end
10987
end

lib/cocoapods_plugin.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
11
# frozen_string_literal: true
22

33
require 'cocoapods_amicable'
4-
5-
Pod::HooksManager.register 'cocoapods-amicable', :post_install do |post_install_context|
6-
CocoaPodsAmicable::PodfileChecksumFixer.new(post_install_context).fix!
7-
end

spec/cocoapods_amicable_spec.rb

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,119 @@
11
# frozen_string_literal: true
22

33
require 'cocoapods_amicable'
4+
require 'tmpdir'
45

5-
RSpec.describe CocoaPodsAmicable::PodfileChecksumFixer do
6+
RSpec.describe CocoaPodsAmicable do
7+
context 'with a user project' do
8+
around(:each) do |t|
9+
Dir.chdir(Dir.mktmpdir) { t.run }
10+
end
11+
12+
let(:config) { Pod::Config.instance }
13+
14+
let(:podfile_content) { <<-RUBY.strip_heredoc }
15+
plugin 'cocoapods-amicable'
16+
target 'App'
17+
RUBY
18+
19+
before do
20+
user_project = Xcodeproj::Project.new('App.xcodeproj')
21+
Pod::Generator::AppTargetHelper.add_app_target(user_project, :osx, '10.11')
22+
user_project.save
23+
24+
File.write 'Podfile', podfile_content
25+
26+
CLAide::Command::PluginManager.load_plugins('cocoapods')
27+
end
28+
29+
it 'writes something' do
30+
config.verbose = true
31+
installer = Pod::Installer.new(config.sandbox, config.podfile, config.lockfile)
32+
installer.install!
33+
34+
expect(File.read('Podfile.lock')).to eq <<-YAML.strip_heredoc
35+
COCOAPODS: #{Pod::VERSION}
36+
YAML
37+
expect(File.read('Pods/Podfile.sha1')).to eq <<-EOS.strip_heredoc
38+
f81a4392854a0fca54d10ebc66e8bc82bc03281a Podfile
39+
EOS
40+
41+
expect(installer.aggregate_targets.flat_map(&:user_targets)).to all(satisfy do |target|
42+
manifest_phase = target.build_phases.find { |bp| bp.name == '[CP] Check Pods Manifest.lock' }
43+
expect(manifest_phase.shell_script).to eq <<-SH.strip_heredoc
44+
set -e
45+
set -u
46+
set -o pipefail
47+
48+
fail() {
49+
# print error to STDERR
50+
echo "error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation." $@ >&2
51+
exit 1
52+
}
53+
54+
diff -q "${PODS_PODFILE_DIR_PATH}/Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null || fail "The manifest in the sandbox differs from your lockfile."
55+
56+
if [ -f "${PODS_ROOT}/Podfile.sha1" ]; then
57+
(cd "${PODS_PODFILE_DIR_PATH}" && shasum --algorithm 1 --status --check "${PODS_ROOT}/Podfile.sha1") || fail "Your Podfile has been changed since the last time you ran 'pod install'."
58+
fi
59+
60+
# This output is used by Xcode 'outputs' to avoid re-running this script phase.
61+
echo "SUCCESS" > "${SCRIPT_OUTPUT_FILE_0}"
62+
SH
63+
expect(manifest_phase.input_paths).to eq [
64+
'${PODS_PODFILE_DIR_PATH}/Podfile.lock',
65+
'${PODS_ROOT}/Manifest.lock',
66+
'${PODS_ROOT}/Podfile.sha1',
67+
'${PODS_PODFILE_DIR_PATH}/Podfile'
68+
]
69+
expect(manifest_phase.output_paths).to eq %w[
70+
$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt
71+
]
72+
end)
73+
74+
expect(Pod::UI.output)
75+
.to include 'Writing Podfile checksum in `Pods/Podfile.sha1`'
76+
end
77+
78+
context 'when the plugin is not specified' do
79+
let(:podfile_content) { super().gsub(/plugin.+/, '') }
80+
81+
it 'keeps the checksum in the lockfile' do
82+
config.verbose = true
83+
installer = Pod::Installer.new(config.sandbox, config.podfile, config.lockfile)
84+
installer.install!
85+
86+
expect(File.read('Podfile.lock')).to eq <<-YAML.strip_heredoc
87+
PODFILE CHECKSUM: 592f3ceb65a6adde4fcbc481f1e0325e951f85e5
88+
89+
COCOAPODS: #{Pod::VERSION}
90+
YAML
91+
expect(File.file?('Pods/Podfile.sha1')).to eq false
92+
93+
expect(installer.aggregate_targets.flat_map(&:user_targets)).to all(satisfy do |target|
94+
manifest_phase = target.build_phases.find { |bp| bp.name == '[CP] Check Pods Manifest.lock' }
95+
expect(manifest_phase.shell_script).to eq <<-SH.strip_heredoc
96+
diff "${PODS_PODFILE_DIR_PATH}/Podfile.lock" "${PODS_ROOT}/Manifest.lock" > /dev/null
97+
if [ $? != 0 ] ; then
98+
# print error to STDERR
99+
echo "error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation." >&2
100+
exit 1
101+
fi
102+
# This output is used by Xcode 'outputs' to avoid re-running this script phase.
103+
echo "SUCCESS" > "${SCRIPT_OUTPUT_FILE_0}"
104+
SH
105+
expect(manifest_phase.input_paths).to eq [
106+
'${PODS_PODFILE_DIR_PATH}/Podfile.lock',
107+
'${PODS_ROOT}/Manifest.lock'
108+
]
109+
expect(manifest_phase.output_paths).to eq %w[
110+
$(DERIVED_FILE_DIR)/Pods-App-checkManifestLockResult.txt
111+
]
112+
end)
113+
114+
expect(Pod::UI.output)
115+
.not_to include 'Writing Podfile checksum in `Pods/Podfile.sha1`'
116+
end
117+
end
118+
end
6119
end

0 commit comments

Comments
 (0)