Commit 9f06feed authored by Samuel E. Giddins's avatar Samuel E. Giddins

Merge pull request #4105 from CocoaPods/seg-validate-import

[Validator] Validate that apps can import the Pod
parents 4c7ce87f d5dcb0a6
......@@ -30,6 +30,11 @@ To install release candidates run `[sudo] gem install cocoapods --pre`
[Samuel Giddins](https://github.com/segiddins)
[#3428](https://github.com/CocoaPods/CocoaPods/issues/3428)
* The validator will now attempt to build an app that imports the pod.
[Samuel Giddins](https://github.com/segiddins)
[#2095](https://github.com/CocoaPods/CocoaPods/issues/2095)
[#2134](https://github.com/CocoaPods/CocoaPods/issues/2134)
##### Bug Fixes
* Improve repo lint error message when no repo found with given name.
......
......@@ -164,7 +164,7 @@ module Pod
validator.validate
unless @clean
UI.puts "Pods project available at `#{validator.validation_dir}/Pods/Pods.xcodeproj` for inspection."
UI.puts "Pods workspace available at `#{validator.validation_dir}/App.xcworkspace` for inspection."
UI.puts
end
if validator.validated?
......
......@@ -61,7 +61,7 @@ module Pod
failure_reasons << validator.failure_reason
unless @clean
UI.puts "Pods project available at `#{validator.validation_dir}/Pods/Pods.xcodeproj` for inspection."
UI.puts "Pods workspace available at `#{validator.validation_dir}/App.xcworkspace` for inspection."
UI.puts
end
end
......
......@@ -247,12 +247,12 @@ module Pod
if config.integrate_targets?
target_inspection = result.target_inspections[target_definition]
target.user_project = target_inspection.project
target.client_root = target.user_project_path.dirname
target.client_root = target.user_project_path.dirname.realpath
target.user_target_uuids = target_inspection.project_target_uuids
target.user_build_configurations = target_inspection.build_configurations
target.archs = target_inspection.archs
else
target.client_root = config.installation_root
target.client_root = config.installation_root.realpath
target.user_target_uuids = []
target.user_build_configurations = target_definition.build_configurations || { 'Release' => :release, 'Debug' => :debug }
if target_definition.platform && target_definition.platform.name == :osx
......
......@@ -257,18 +257,25 @@ module Pod
UI.message "\n\n#{spec} - Analyzing on #{platform} platform.".green.reversed
@consumer = spec.consumer(platform)
setup_validation_environment
begin
create_app_project
download_pod
check_file_patterns
install_pod
add_app_project_import
validate_vendored_dynamic_frameworks
build_pod
ensure
tear_down_validation_environment
end
validated?
end
return false if fail_fast && !valid
perform_extensive_subspec_analysis(spec) unless @no_subspecs
rescue => e
error('unknown', "Encountered an unknown error (#{e}) during validation.")
message = e.to_s
message << "\n" << e.backtrace.join("\n") << "\n" if config.verbose?
error('unknown', "Encountered an unknown error (#{message}) during validation.")
false
end
......@@ -340,10 +347,10 @@ module Pod
validation_dir.mkpath
@original_config = Config.instance.clone
config.installation_root = validation_dir
config.sandbox_root = validation_dir + 'Pods'
config.silent = !config.verbose
config.integrate_targets = false
config.integrate_targets = true
config.skip_repo_update = true
config.deterministic_uuids = false
end
def tear_down_validation_environment
......@@ -361,13 +368,58 @@ module Pod
@file_accessor = @installer.pod_targets.flat_map(&:file_accessors).find { |fa| fa.spec.name == consumer.spec.name }
end
def create_app_project
app_project = Xcodeproj::Project.new(validation_dir + 'App.xcodeproj')
deployment_target = spec.subspec_by_name(subspec_name).deployment_target(consumer.platform_name)
app_project.new_target(:application, 'App', consumer.platform_name, deployment_target)
app_project.save
app_project.recreate_user_schemes
Xcodeproj::XCScheme.share_scheme(app_project.path, 'App')
end
def add_app_project_import
app_project = Xcodeproj::Project.open(validation_dir + 'App.xcodeproj')
pod_target = @installer.pod_targets.find { |pt| pt.pod_name == spec.root.name }
source_file = write_app_import_source_file(pod_target)
source_file_ref = app_project.new_group('App', 'App').new_file(source_file)
app_project.targets.first.add_file_references([source_file_ref])
app_project.save
end
def write_app_import_source_file(pod_target)
language = pod_target.uses_swift? ? :swift : :objc
if language == :swift
source_file = validation_dir.+('App/main.swift')
source_file.parent.mkpath
import_statement = use_frameworks ? "import #{pod_target.product_module_name}\n" : ''
source_file.open('w') { |f| f << import_statement }
else
source_file = validation_dir.+('App/main.m')
source_file.parent.mkpath
import_statement = if use_frameworks
"@import #{pod_target.product_module_name};\n"
else
header_name = "#{pod_target.product_module_name}/#{pod_target.product_module_name}.h"
if pod_target.sandbox.public_headers.root.+(header_name).file?
"#import <#{header_name}>\n"
else
''
end
end
source_file.open('w') { |f| f << "@import Foundation;\n#{import_statement}int main() {}\n" }
end
source_file
end
# It creates a podfile in memory and builds a library containing the pod
# for all available platforms with xcodebuild.
#
def install_pod
%i(determine_dependency_product_types verify_no_duplicate_framework_names
verify_no_static_framework_transitive_dependencies
verify_framework_usage generate_pods_project
verify_framework_usage generate_pods_project integrate_user_project
perform_post_install_actions).each { |m| @installer.send(m) }
deployment_target = spec.subspec_by_name(subspec_name).deployment_target(consumer.platform_name)
......@@ -412,7 +464,7 @@ module Pod
UI.warn "Skipping compilation with `xcodebuild' because it can't be found.\n".yellow
else
UI.message "\nBuilding with xcodebuild.\n".yellow do
output = Dir.chdir(config.sandbox_root) { xcodebuild }
output = xcodebuild
UI.puts output
parsed_output = parse_xcodebuild_output(output)
parsed_output.each do |message|
......@@ -631,18 +683,17 @@ module Pod
# returns its output (both STDOUT and STDERR).
#
def xcodebuild
command = 'xcodebuild clean build -target Pods'
command = %w(clean build -workspace App.xcworkspace -scheme App)
case consumer.platform_name
when :ios
command << ' CODE_SIGN_IDENTITY=- -sdk iphonesimulator'
command += %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator)
when :watchos
command << ' CODE_SIGN_IDENTITY=- -sdk watchsimulator'
command += %w(CODE_SIGN_IDENTITY=- -sdk watchsimulator)
when :tvos
command << ' CODE_SIGN_IDENTITY=- -sdk appletvsimulator'
command += %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator)
end
output, status = _xcodebuild "#{command} 2>&1"
output, status = Dir.chdir(validation_dir) { _xcodebuild(command) }
unless status.success?
message = 'Returned an unsuccessful exit code.'
......@@ -659,9 +710,8 @@ module Pod
# resulting status.
#
def _xcodebuild(command)
UI.puts command if config.verbose
output = `#{command}`
[output, $?]
UI.puts 'xcodebuild ' << command.join(' ') if config.verbose
Executable.capture_command('xcodebuild', command, :capture => :merge)
end
#-------------------------------------------------------------------------#
......
......@@ -104,7 +104,7 @@ module Pod
run_command('lib', 'lint', 'Broken.podspec', '--no-clean')
end
UI.output.should.include 'Missing required attribute'
UI.output.should.include 'Pods project available at'
UI.output.should.include 'Pods workspace available at'
end
end
......
......@@ -130,6 +130,7 @@ module Pod
Installer.any_instance.stubs(:aggregate_targets).returns([])
Installer.any_instance.stubs(:pod_targets).returns([])
Validator.any_instance.stubs(:install_pod)
Validator.any_instance.stubs(:add_app_project_import)
Validator.any_instance.stubs(:check_file_patterns)
Validator.any_instance.stubs(:validated?).returns(true)
Validator.any_instance.stubs(:validate_url)
......
......@@ -102,6 +102,7 @@ module Pod
@validator.stubs(:download_pod)
@validator.stubs(:check_file_patterns)
@validator.stubs(:install_pod)
@validator.stubs(:add_app_project_import)
@validator.stubs(:build_pod)
@validator.stubs(:tear_down_validation_environment)
WebMock::API.stub_request(:head, /not-found/).to_return(:status => 404)
......@@ -128,7 +129,7 @@ module Pod
WebMock::API.stub_request(:head, /found/).to_return(:status => 200)
Specification.any_instance.stubs(:homepage).returns('http://banana-corp.local/redirect/')
@validator.validate
@validator.results.length.should.equal 0
@validator.results.should.be.empty
end
it 'does not fail if the homepage does not support HEAD' do
......@@ -136,7 +137,7 @@ module Pod
WebMock::API.stub_request(:get, /page/).to_return(:status => 200)
Specification.any_instance.stubs(:homepage).returns('http://banana-corp.local/page/')
@validator.validate
@validator.results.length.should.equal 0
@validator.results.should.be.empty
end
it 'does not fail if the homepage errors on HEAD' do
......@@ -144,7 +145,7 @@ module Pod
WebMock::API.stub_request(:get, /page/).to_return(:status => 200)
Specification.any_instance.stubs(:homepage).returns('http://banana-corp.local/page/')
@validator.validate
@validator.results.length.should.equal 0
@validator.results.should.be.empty
end
it 'does not follow redirects infinitely' do
......@@ -166,7 +167,7 @@ module Pod
Specification.any_instance.stubs(:homepage).returns(
'http://banana-corp.local/redirect')
@validator.validate
@validator.results.length.should.equal 0
@validator.results.should.be.empty
end
end
......@@ -300,8 +301,8 @@ module Pod
validator.stubs(:validate_url)
validator.stubs(:validate_screenshots)
validator.stubs(:check_file_patterns)
validator.stubs(:check_file_patterns)
validator.stubs(:install_pod)
validator.stubs(:add_app_project_import)
%i(prepare resolve_dependencies download_dependencies).each do |m|
Installer.any_instance.stubs(m)
end
......@@ -404,11 +405,11 @@ module Pod
git = Executable.which(:git)
Executable.stubs(:which).with('git').returns(git)
Executable.expects(:which).with('xcodebuild').times(4).returns('/usr/bin/xcodebuild')
command = 'xcodebuild clean build -target Pods'
validator.expects(:`).with("#{command} 2>&1").once.returns('')
validator.expects(:`).with("#{command} CODE_SIGN_IDENTITY=- -sdk appletvsimulator 2>&1").once.returns('')
validator.expects(:`).with("#{command} CODE_SIGN_IDENTITY=- -sdk iphonesimulator 2>&1").once.returns('')
validator.expects(:`).with("#{command} CODE_SIGN_IDENTITY=- -sdk watchsimulator 2>&1").once.returns('')
command = %w(clean build -workspace App.xcworkspace -scheme App)
Executable.expects(:capture_command).with('xcodebuild', command, :capture => :merge).once.returns(['', stub(:success? => true)])
Executable.expects(:capture_command).with('xcodebuild', command + %w(CODE_SIGN_IDENTITY=- -sdk appletvsimulator), :capture => :merge).once.returns(['', stub(:success? => true)])
Executable.expects(:capture_command).with('xcodebuild', command + %w(CODE_SIGN_IDENTITY=- -sdk iphonesimulator), :capture => :merge).once.returns(['', stub(:success? => true)])
Executable.expects(:capture_command).with('xcodebuild', command + %w(CODE_SIGN_IDENTITY=- -sdk watchsimulator), :capture => :merge).once.returns(['', stub(:success? => true)])
validator.validate
end
......@@ -444,6 +445,113 @@ module Pod
validator.results.count.should == 0
end
describe 'import validation' do
before do
@validator = Validator.new(podspec_path, SourcesManager.master.map(&:url))
@validator.stubs(:validate_url)
@consumer = Specification.from_file(podspec_path).consumer(:ios)
@validator.instance_variable_set(:@consumer, @consumer)
@validator.send(:setup_validation_environment)
end
after do
@validator.send(:tear_down_validation_environment)
end
it 'creates an empty app project & target to integrate into' do
@validator.send(:create_app_project)
project = Xcodeproj::Project.open(@validator.validation_dir + 'App.xcodeproj')
target = project.native_targets.find { |t| t.name == 'App' }
target.should.not.be.nil
target.symbol_type.should == :application
target.deployment_target.should.be.nil
target.platform_name.should == :ios
Xcodeproj::Project.schemes(project.path).should == %w(App)
end
describe 'creating the importing file' do
describe 'when linting as a framework' do
before do
@validator.stubs(:use_frameworks).returns(true)
end
it 'creates a swift import' do
pod_target = stub(:uses_swift? => true, :product_module_name => 'ModuleName')
file = @validator.send(:write_app_import_source_file, pod_target)
file.basename.to_s.should == 'main.swift'
file.read.should == <<-SWIFT.strip_heredoc
import ModuleName
SWIFT
end
it 'creates an objective-c import' do
pod_target = stub(:uses_swift? => false, :product_module_name => 'ModuleName')
file = @validator.send(:write_app_import_source_file, pod_target)
file.basename.to_s.should == 'main.m'
file.read.should == <<-OBJC.strip_heredoc
@import Foundation;
@import ModuleName;
int main() {}
OBJC
end
end
describe 'when linting as a static lib' do
before do
@validator.stubs(:use_frameworks).returns(false)
@sandbox = config.sandbox
end
it 'creates an objective-c import when a plausible umbrella header is found' do
pod_target = stub(:uses_swift? => false, :product_module_name => 'ModuleName', :sandbox => @sandbox)
header_name = "#{pod_target.product_module_name}/#{pod_target.product_module_name}.h"
umbrella = pod_target.sandbox.public_headers.root.+(header_name)
umbrella.dirname.mkpath
umbrella.open('w') {}
file = @validator.send(:write_app_import_source_file, pod_target)
file.basename.to_s.should == 'main.m'
file.read.should == <<-OBJC.strip_heredoc
@import Foundation;
#import <ModuleName/ModuleName.h>
int main() {}
OBJC
end
it 'does not create an objective-c import when no umbrella header is found' do
pod_target = stub(:uses_swift? => false, :product_module_name => 'ModuleName', :sandbox => @sandbox)
file = @validator.send(:write_app_import_source_file, pod_target)
file.basename.to_s.should == 'main.m'
file.read.should == <<-OBJC.strip_heredoc
@import Foundation;
int main() {}
OBJC
end
end
end
it 'adds the importing file to the app target' do
@validator.stubs(:use_frameworks).returns(true)
@validator.send(:create_app_project)
pod_target = stub(:uses_swift? => true, :pod_name => 'JSONKit', :product_module_name => 'ModuleName')
installer = stub(:pod_targets => [pod_target])
@validator.instance_variable_set(:@installer, installer)
@validator.send(:add_app_project_import)
project = Xcodeproj::Project.open(@validator.validation_dir + 'App.xcodeproj')
group = project['App']
file = group.find_file_by_path('main.swift')
file.should.not.be.nil
target = project.native_targets.find { |t| t.name == 'App' }
target.source_build_phase.files_references.should.include(file)
end
end
describe 'file pattern validation' do
it 'checks for file patterns' do
file = write_podspec(stub_podspec(/.*source_files.*/, '"source_files": "wrong_paht.*",'))
......@@ -538,6 +646,7 @@ module Pod
@validator.stubs(:validate_screenshots)
@validator.stubs(:check_file_patterns)
@validator.stubs(:install_pod)
@validator.stubs(:add_app_project_import)
%i(prepare resolve_dependencies download_dependencies).each do |m|
Installer.any_instance.stubs(m)
end
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment