Commit b825e2e1 authored by Fabio Pelosin's avatar Fabio Pelosin

Rename Advanced Linter to Validator and integrate with Core extraction.

parent a37c3c4c
......@@ -32,6 +32,7 @@ module Pod
autoload :Resolver, 'cocoapods/resolver'
autoload :Sandbox, 'cocoapods/sandbox'
autoload :UI, 'cocoapods/user_interface'
autoload :Validator, 'cocoapods/validator'
autoload :Pathname, 'pathname'
......
......@@ -7,7 +7,6 @@ module Pod
end
class Command < CLAide::Command
autoload :AdvancedLinter, 'cocoapods/command/advanced_linter'
self.abstract_command = true
self.command = 'pod'
......
module Pod
class Command
# Extends the Linter from the Core to add additional which require the
# LocalPod and the Installer.
#
# In detail it checks that the file patterns defined by the user match
# actually do match at least a file and that the Pod builds, by installing
# it without integration and building the project with xcodebuild.
#
class AdvancedLinter
include Config::Mixin
# @param [Specification, Pathname, String] spec_or_path
# the Specification or the path of the `podspec` file to lint.
#
def initialize(spec_or_path)
@simple_linter = Specification::Linter.new(spec_or_path)
end
# @return [Specification] the specification to lint.
#
def spec
@simple_linter.spec
end
# @return [Pathname] the path of the `podspec` file where {#spec} is
# defined.
#
def file
@simple_linter.file
end
# @return [Specification::Linter] the linter instance from CocoaPods
# Core.
#
attr_reader :simple_linter
# Lints the specification adding a {Result} for any failed check to the
# {#results} list.
#
# @return [Bool] whether the specification passed validation.
#
def lint
# Show immediately which pod is being processed.
print " -> #{spec.name}\r" unless config.silent?
$stdout.flush
simple_linter.lint
unless quick
check_repo_path
spec.available_platforms.each do |platform|
UI.section "\n\n#{spec} - Analyzing on #{platform} platform.".green.reversed do
# current_platform = platform
# set_up_lint_environment
# install_pod
# build_pod
# check_file_patterns
# tear_down_lint_environment
end
end
end
# This overwrites the previously printed text
UI.puts " -> ".send(color) << spec.name unless config.silent?
print_messages('ERROR', simple_linter.errors)
print_messages('WARN', simple_linter.warnings)
print_messages('NOTE', simple_linter.notes)
UI.puts unless config.silent?
errors.empty? && warnings.empty? && deprecations.empty?
end
#-----------------------------------------------------------------------#
# @!group Configuration
# @return [Bool] whether the lint should skip the checks that requires
# the download or the build of the library.
#
attr_accessor :quick
# @return [Bool] whether the linter should not clean up temporary files
# for inspection.
#
attr_accessor :no_clean
# @return [Pathname] whether the lint should be performed
#
attr_accessor :repo_path
# @return [Bool] whether the lint should be performed against the root of
# the podspec instead to its original source. Uses the `:local` option
# of the Podfile.
#
attr_writer :local
def local?; @local; end
#-----------------------------------------------------------------------#
# !@group Lint results
public
# @return [Array<Result>] all the notes generated by the Linter.
#
def notes
@errors ||= results.select { |r| r.type == :note }
end
# TODO
def result_type
:note
end
#-----------------------------------------------------------------------#
private
# !@group Lint steps
def check_repo_path
return unless repo_path
expected_path = "#{spec.name}/#{spec.version}/#{spec.name}.podspec"
path = file.relative_path_from(repo_path).to_s
unless path.end_with?(expected_path)
error "Incorrect path, the path is `#{file}` and should be `#{expected_path}`"
end
end
def set_up_lint_environment
tmp_dir.rmtree if tmp_dir.exist?
tmp_dir.mkpath
@original_config = Config.instance.clone
config.project_root = tmp_dir
config.project_pods_root = tmp_dir + 'Pods'
config.silent = !config.verbose
config.integrate_targets = false
config.generate_docs = false
end
def tear_down_lint_environment
tmp_dir.rmtree unless no_clean
Config.instance = @original_config
end
# It creates a podfile in memory and builds a library containing
# the pod for all available platforms with xcodebuild.
#
def install_pod
spec.activate_platform(current_platform)
podfile = podfile_from_spec
config.verbose
config.skip_repo_update = true
sandbox = Sandbox.new(config.project_pods_root)
resolver = Resolver.new(podfile, nil, sandbox)
installer = Installer.new(resolver)
installer.install!
@pod = installer.pods.find { |pod| pod.top_specification == spec }
config.silent
end
# Performs platform specific analysis.
# It requires to download the source at each iteration
#
# @note Treat xcodebuild warnings as notes because the spec maintainer
# might not be the author of the library
#
def build_pod
if `which xcodebuild`.strip.empty?
UI.warn "Skipping compilation with `xcodebuild' because it can't be found.\n".yellow
else
UI.message "\nBuilding with xcodebuild.\n".yellow if config.verbose? do
messages = []
output = Dir.chdir(config.project_pods_root) { `xcodebuild clean build 2>&1` }
clean_output = parse_xcodebuild_output(output)
messages += clean_output
puts(output) if config.verbose?
messages.each { |msg| ( msg.include?('error: ') ? @platform_errors[@platform] : @platform_notes[@platform] ) << msg }
end
end
end
# It checks that every file pattern specified in a spec yields
# at least one file. It requires the pods to be already present
# in the current working directory under Pods/spec.name.
#
# @return [Array<String>]
#
def check_file_patterns
error "The sources did not match any file" if !spec.source_files.empty? && @pod.source_files.empty?
error "The resources did not match any file" if !spec.resources.empty? && @pod.resource_files.empty?
error "The preserve_paths did not match any file" if !spec.preserve_paths.empty? && @pod.preserve_files.empty?
error "The exclude_header_search_paths did not match any file" if !spec.exclude_header_search_paths.empty? && @pod.headers_excluded_from_search_paths.empty?
unless @pod.license_file || spec.license && ( spec.license[:type] == 'Public Domain' || spec.license[:text] )
warning "Unable to find a license file"
end
end
# @ errors += (@platform_errors[platform] - @errors).map {|m| "[#{platform}] #{m}"}
#-----------------------------------------------------------------------#
private
# !@group Helpers
# @return [Podfile] a podfile that requires the specification on the
# current platform.
#
# @note The generated podfile takes into account whether the linter is
# in local mode.
#
def podfile_from_spec
name = spec.name
podspec = file.realpath
platform = current_platform
local = local?
podfile = Pod::Podfile.new do
platform(platform.to_sym, platform.deployment_target)
if (local)
pod name, :local => podspec.dirname.to_s
else
pod name, :podspec => podspec.to_s
end
end
podfile
end
# @return [Pathname] the temporary directory used by the linter.
#
def tmp_dir
Pathname.new('/tmp/CocoaPods/Lint')
end
# @return [Pathname] the root of the installed pod.
#
def pod_dir
tmp_dir + 'Pods' + spec.name
end
# Parse the xcode build output to identify the lines which are relevant
# to the linter. It also removes the indentation and the temporary path.
#
# @param [String] output the output generated by the xcodebuild tool.
#
# @return [Array<String>] the lines that are relevant to the linter.
#
def parse_xcodebuild_output(output)
lines = output.split("\n")
selected_lines = lines.select do |l|
l.include?('error: ') &&
(l !~ /errors? generated\./) && (l !~ /error: \(null\)/) ||
l.include?('warning: ') && (l !~ /warnings? generated\./) ||
l.include?('note: ') && (l !~ /expanded from macro/)
end
selected_lines.map do |l|
new = l.gsub(/\/tmp\/CocoaPods\/Lint\/Pods\//,'') # Remove the unnecessary tmp path
new.gsub!(/^ */,' ') # Remove indentation
"XCODEBUILD > " << new # Mark
end
end
#
#
def print_messages(type, messages)
return if config.silent?
messages.each {|msg| UI.puts " - #{type.ljust(5)} | #{msg}"}
end
end
end
end
......@@ -107,7 +107,7 @@ module Pod
invalid_count = 0
podspecs.each do |podspec|
linter = AdvancedLinter.new(podspec)
linter = Validator.new(podspec)
linter.quick = true
linter.repo_path = dir
......
......@@ -53,6 +53,8 @@ module Pod
end
end
#-----------------------------------------------------------------------#
class Lint < Spec
self.summary = 'Validates a spec file.'
......@@ -84,21 +86,13 @@ module Pod
UI.puts
invalid_count = 0
podspecs_to_lint.each do |podspec|
linter = AdvancedLinter.new(podspec)
linter.quick = @quick
linter.local = @local
linter.no_clean = @no_clean
case linter.result_type
when :error
invalid_count += 1
color = :red
when :warning
invalid_count += 1 unless @only_errors
color = :yellow
else
color = :green
end
validator = Validator.new(podspec)
validator.quick = @quick
validator.local = @local
validator.no_clean = @no_clean
validator.validate
invalid_count += 1 unless validator.validated?
end
count = podspecs_to_lint.count
......@@ -113,6 +107,8 @@ module Pod
end
end
#-----------------------------------------------------------------------#
class Cat < Spec
self.summary = 'Prints a spec file'
......
......@@ -690,7 +690,8 @@ module Pod
# of using a script.
#
def integrate_user_project
UserProjectIntegrator.new(podfile, pods_project, config.project_root).integrate! if config.integrate_targets?
return unless config.integrate_targets?
UserProjectIntegrator.new(podfile, pods_project, config.project_root).integrate!
end
end
end
......@@ -316,6 +316,8 @@ module Pod
paths_by_spec.values.flatten
end
alias :resources :resource_files
# @return [Array<Pathname>] The *relative* paths of the resources.
#
def relative_resource_files
......@@ -339,6 +341,8 @@ module Pod
paths
end
alias :preserve_paths :preserve_files
# @return [Pathname] The automatically detected absolute path of the README
# file.
#
......
......@@ -309,9 +309,15 @@ module Pod
#
# @return [String] the computed path.
#
# @todo: Check integrate_targets conditional.
#
def relative_to_srcroot(path = nil)
base_path = path ? config.project_pods_root + path : config.project_pods_root
if config.integrate_targets?
(base_path).relative_path_from(user_project_path.dirname).to_s
else
(base_path).relative_path_from(config.project_root).to_s
end
end
def relative_pods_root
......
......@@ -4,7 +4,6 @@ module Pod
include Config::Mixin
# @return [Array<Source>] the list of all the sources known to this
# installation of CocoaPods.
#
......
This diff is collapsed.
require File.expand_path('../../../spec_helper', __FILE__)
module Pod
describe Command::AdvancedLinter do
extend SpecHelper::TemporaryDirectory
def write_podspec(text, name = 'JSONKit.podspec')
file = temporary_directory + 'JSONKit.podspec'
File.open(file, 'w') {|f| f.write(text) }
file
end
def stub_podspec(pattern = nil, replacement = nil)
spec = (fixture('spec-repos') + 'master/JSONKit/1.4/JSONKit.podspec').read
spec.gsub!(/https:\/\/github\.com\/johnezang\/JSONKit\.git/, fixture('integration/JSONKit').to_s)
spec.gsub!(pattern, replacement) if pattern && replacement
spec
end
it "respects quick mode" do
file = write_podspec(stub_podspec)
linter = Command::AdvancedLinter.new(file)
linter.expects(:peform_multiplatform_analysis).never
linter.expects(:install_pod).never
linter.expects(:xcodebuild_output_for_platfrom).never
linter.expects(:file_patterns_errors_for_platfrom).never
linter.quick = true
linter.lint
end
unless skip_xcodebuild?
it "uses xcodebuild to generate notes and warnings" do
file = write_podspec(stub_podspec)
linter = Command::AdvancedLinter.new(file)
linter.lint
linter.result_type.should == :warning
linter.notes.join(' | ').should.include "JSONKit/JSONKit.m:1640:27: warning: equality comparison with extraneous parentheses"
end
end
it "checks for file patterns" do
file = write_podspec(stub_podspec(/s\.source_files = 'JSONKit\.\*'/, "s.source_files = 'JSONKit.*'\ns.resources = 'WRONG_FOLDER'"))
linter = Command::AdvancedLinter.new(file)
linter.stubs(:xcodebuild_output).returns([])
linter.quick = false
linter.lint
linter.result_type.should == :error
linter.errors.join(' | ').should.include "The resources did not match any file"
end
it "uses the deployment target of the specification" do
file = write_podspec(stub_podspec(/s.name *= 'JSONKit'/, "s.name = 'JSONKit'; s.platform = :ios, '5.0'"))
linter = Command::AdvancedLinter.new(file)
linter.quick = true
linter.lint
podfile = linter.podfile_from_spec
deployment_target = podfile.target_definitions[:default].platform.deployment_target
deployment_target.to_s.should == "5.0"
end
end
end
require File.expand_path('../../spec_helper', __FILE__)
module Pod
describe Validator do
extend SpecHelper::TemporaryDirectory
# @return [void]
#
def write_podspec(text, name = 'JSONKit.podspec')
file = temporary_directory + 'JSONKit.podspec'
File.open(file, 'w') {|f| f.write(text) }
file
end
# @return [String]
#
def stub_podspec(pattern = nil, replacement = nil)
spec = (fixture('spec-repos') + 'master/JSONKit/1.4/JSONKit.podspec').read
spec.gsub!(/https:\/\/github\.com\/johnezang\/JSONKit\.git/, fixture('integration/JSONKit').to_s)
spec.gsub!(pattern, replacement) if pattern && replacement
spec
end
# @return [Pathname]
#
def podspec_path
fixture('spec-repos') + 'master/JSONKit/1.4/JSONKit.podspec'
end
#-------------------------------------------------------------------------#
it "validates a correct podspec" do
validator = Validator.new(podspec_path)
validator.repo_path = fixture('spec-repos/master')
validator.quick = true
validator.validate
validator.results.should == []
validator.validated?.should.be.true
end
it "lints the podspec during validation" do
podspec = stub_podspec(/s.name.*$/, 's.name = "TEST"')
file = write_podspec(podspec)
validator = Validator.new(file)
validator.quick = true
validator.validate
validator.results.map(&:to_s).first.should.match /should match the name/
validator.validated?.should.be.false
end
it "checks the path of the specification if a repo path is provided" do
validator = Validator.new(podspec_path)
validator.quick = true
validator.repo_path = fixture('.')
validator.validate
validator.results.map(&:to_s).first.should.match /Incorrect path/
validator.validated?.should.be.false
end
unless skip_xcodebuild?
it "uses xcodebuild to generate notes and warnings" do
validator = Validator.new(podspec_path)
validator.stubs(:check_file_patterns)
validator.validate
first = validator.results.map(&:to_s).first
first.should.include "[NOTE] XCODEBUILD"
first.should.include "JSONKit/JSONKit.m:1640:27: warning: equality comparison"
first.should.include "[OS X - iOS]"
validator.result_type.should == :note
end
end
it "checks for file patterns" do
file = write_podspec(stub_podspec(/s\.source_files = 'JSONKit\.\*'/, "s.source_files = 'wrong_paht.*'"))
validator = Validator.new(file)
validator.stubs(:build_pod)
validator.validate
validator.results.map(&:to_s).first.should.match /source_files.*did not match/
validator.result_type.should == :error
end
#--------------------------------------#
it "respects quick mode" do
file = write_podspec(stub_podspec)
validator = Validator.new(file)
validator.quick = true
validator.expects(:perform_extensive_analysis).never
validator.validate
end
it "respects the no clean option" do
file = write_podspec(stub_podspec)
validator = Validator.new(file)
validator.no_clean = true
validator.validate
validator.validation_dir.should.exist
end
it "respects the local option" do
validator = Validator.new(podspec_path)
podfile = validator.send(:podfile_from_spec, Platform.new(:ios, '5.0'))
deployment_target = podfile.target_definitions[:default].platform.deployment_target
deployment_target.to_s.should == "5.0"
end
it "respects the only errors option" do
podspec = stub_podspec(/s.summary.*/, "s.summary = 'A short description of'")
file = write_podspec(podspec)
validator = Validator.new(file)
validator.quick = true
validator.only_errors = true
validator.validate
validator.results.map(&:to_s).first.should.match /summary.*meaningful/
validator.validated?.should.be.true
end
it "uses the deployment target of the specification" do
validator = Validator.new(podspec_path)
podfile = validator.send(:podfile_from_spec, Platform.new(:ios, '5.0'))
dependency = podfile.target_definitions[:default].dependencies.first
dependency.external_source.has_key?(:podspec).should.be.true
end
end
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