Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional Xcode 7.3 compatibility improvements #174

Merged
merged 5 commits into from
Mar 29, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: objective-c
script: bundle exec rake
osx_image: xcode7
osx_image: xcode7.3

# Sets Travis to run the Ruby specs on OS X machines which are required to
# build the native extensions of Xcodeproj.
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# CHANGELOG

## master
* Product info is now read from schemes. Specify a scheme in `.slather.yml` or with the `--scheme` argument to ensure consistent results.
* Automatically detect the derived data directory from `xcodebuild`
[Kent Sutherland](https://github.com/ksuther)
[#174](https://github.com/SlatherOrg/slather/pull/174)

* Xcode 7.3 compatibility (updated path returned by `profdata_coverage_dir`)
[Kent Sutherland](https://github.com/ksuther)
[#125](https://github.com/SlatherOrg/slather/issues/125), [#169](https://github.com/SlatherOrg/slather/pull/169)
Expand Down
107 changes: 72 additions & 35 deletions lib/slather/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,23 @@ def self.open(xcodeproj)
end

def derived_data_path
File.expand_path('~') + "/Library/Developer/Xcode/DerivedData/"
# Get the derived data path from xcodebuild
# Use OBJROOT when possible, as it provides regardless of whether or not the Derived Data location is customized
if self.scheme
build_settings = `xcodebuild -project "#{self.path}" -scheme "#{self.scheme}" -showBuildSettings`
else
build_settings = `xcodebuild -project "#{self.path}" -showBuildSettings`
end

if build_settings
derived_data_path = build_settings.match(/ OBJROOT = (.+)/)[1]
end

if derived_data_path == nil
derived_data_path = File.expand_path('~') + "/Library/Developer/Xcode/DerivedData/"
end

derived_data_path
end
private :derived_data_path

Expand Down Expand Up @@ -143,21 +159,6 @@ def profdata_file
end
private :profdata_file

def find_binary_file_for_app(app_bundle_file)
app_bundle_file_name_noext = Pathname.new(app_bundle_file).basename.to_s.gsub(".app", "")
Dir["#{app_bundle_file}/**/#{app_bundle_file_name_noext}"].first
end

def find_binary_file_for_dynamic_lib(framework_bundle_file)
framework_bundle_file_name_noext = Pathname.new(framework_bundle_file).basename.to_s.gsub(".framework", "")
"#{framework_bundle_file}/#{framework_bundle_file_name_noext}"
end

def find_binary_file_for_static_lib(xctest_bundle_file)
xctest_bundle_file_name_noext = Pathname.new(xctest_bundle_file).basename.to_s.gsub(".xctest", "")
Dir["#{xctest_bundle_file}/**/#{xctest_bundle_file_name_noext}"].first
end

def unsafe_profdata_llvm_cov_output
profdata_file_arg = profdata_file
if profdata_file_arg == nil
Expand Down Expand Up @@ -288,28 +289,64 @@ def configure_binary_file
end
end

def find_binary_file_in_bundle(bundle_file)
bundle_file_noext = File.basename(bundle_file, File.extname(bundle_file))
Dir["#{bundle_file}/**/#{bundle_file_noext}"].first
end

def find_binary_file
xctest_bundle = Dir["#{profdata_coverage_dir}/**/*.xctest"].reject { |bundle|
bundle.include? "-Runner.app/PlugIns/"
}.first
raise StandardError, "No product binary found in #{profdata_coverage_dir}. Are you sure your project is setup for generating coverage files? Try `slather setup your/project.xcodeproj`" unless xctest_bundle != nil

# Find the matching binary file
search_for = self.binary_basename || self.class.yml["binary_basename"] || '*'
xctest_bundle_file_directory = Pathname.new(xctest_bundle).dirname
app_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.app"].first
dynamic_lib_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.framework"].first
matched_xctest_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.xctest"].first

if app_bundle != nil
find_binary_file_for_app(app_bundle)
elsif dynamic_lib_bundle != nil
find_binary_file_for_dynamic_lib(dynamic_lib_bundle)
elsif matched_xctest_bundle != nil
find_binary_file_for_static_lib(matched_xctest_bundle)
binary_basename = self.binary_basename || self.class.yml["binary_basename"] || nil

# Get scheme info out of the xcodeproj
if self.scheme
schemes_path = Xcodeproj::XCScheme.shared_data_dir(self.path)
xcscheme_path = "#{schemes_path + self.scheme}.xcscheme"
xcscheme = Xcodeproj::XCScheme.new(xcscheme_path)

buildable_name = xcscheme.build_action.entries[0].buildable_references[0].buildable_name
configuration = xcscheme.test_action.build_configuration

search_for = binary_basename || buildable_name
found_product = Dir["#{profdata_coverage_dir}/Products/#{configuration}*/#{search_for}*"].sort { |x, y|
# Sort the matches without the file extension to ensure better matches when there are multiple candidates
# For example, if the binary_basename is Test then we want Test.app to be matched before Test Helper.app
File.basename(x, File.extname(x)) <=> File.basename(y, File.extname(y))
}.reject { |path|
path.end_with? ".dSYM"
}.first

if found_product and File.directory? found_product
found_binary = find_binary_file_in_bundle(found_product)
else
found_binary = found_product
end
else
find_binary_file_for_static_lib(xctest_bundle)
xctest_bundle = Dir["#{profdata_coverage_dir}/**/*.xctest"].reject { |bundle|
# Ignore xctest bundles that are in the UI runner app
bundle.include? "-Runner.app/PlugIns/"
}.first

# Find the matching binary file
search_for = binary_basename || '*'
xctest_bundle_file_directory = Pathname.new(xctest_bundle).dirname
app_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.app"].first
dynamic_lib_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.framework"].first
matched_xctest_bundle = Dir["#{xctest_bundle_file_directory}/#{search_for}.xctest"].first

if app_bundle != nil
found_binary = find_binary_file_in_bundle(app_bundle)
elsif dynamic_lib_bundle != nil
found_binary = find_binary_file_in_bundle(dynamic_lib_bundle)
elsif matched_xctest_bundle != nil
found_binary = find_binary_file_in_bundle(matched_xctest_bundle)
else
found_binary = find_binary_file_in_bundle(xctest_bundle)
end
end

raise StandardError, "No product binary found in #{profdata_coverage_dir}. Are you sure your project is setup for generating coverage files? Try `slather setup your/project.xcodeproj`" unless found_binary != nil

found_binary
end

end
Expand Down
52 changes: 8 additions & 44 deletions spec/slather/project_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,12 @@
Slather::Project.open(FIXTURES_PROJECT_PATH)
end

describe "#derived_data_path" do
it "should return the system's derived data directory" do
expect(fixtures_project.send(:derived_data_path)).to eq(File.expand_path('~') + "/Library/Developer/Xcode/DerivedData/")
end
end

describe "#build_directory" do
it "should return the build_directory property, if it has been explicitly set" do
build_directory_mock = double(String)
fixtures_project.build_directory = build_directory_mock
expect(fixtures_project.build_directory).to eq(build_directory_mock)
end

it "should return the derived_data_path if no build_directory has been set" do
derived_data_path = File.expand_path('~') + "/Library/Developer/Xcode/DerivedData/"
fixtures_project.send(:configure_build_directory)
expect(fixtures_project.build_directory).to eq(derived_data_path)
end
end

describe "::yml" do
Expand Down Expand Up @@ -141,31 +129,15 @@ class SpecXcode7CoverageFile < Slather::ProfdataCoverageFile
allow(Dir).to receive(:[]).and_call_original
allow(fixtures_project).to receive(:build_directory).and_return(build_directory)
allow(fixtures_project).to receive(:input_format).and_return("profdata")
allow(fixtures_project).to receive(:scheme).and_return("FixtureScheme")
allow(fixtures_project).to receive(:scheme).and_return("fixtures")
allow(Dir).to receive(:[]).with("#{build_directory}/**/CodeCoverage/FixtureScheme").and_return(["#{build_directory}/Build/Intermediates/CodeCoverage/FixtureScheme"])
allow(Dir).to receive(:[]).with("#{build_directory}/Build/Intermediates/CodeCoverage/FixtureScheme/**/*.xctest").and_return(["#{build_directory}/Build/Intermediates/CodeCoverage/FixtureScheme/FixtureAppTests.xctest"])
end

it "should return the binary file location for an app bundle provided a scheme" do
allow(Dir).to receive(:[]).with("#{build_directory}/Build/Intermediates/CodeCoverage/FixtureScheme/*.app").and_return(["/FixtureScheme/FixtureApp.app"])
allow(Dir).to receive(:[]).with("/FixtureScheme/FixtureApp.app/**/FixtureApp").and_return(["/FixtureScheme/FixtureApp.app/FixtureApp"])
fixtures_project.send(:configure_binary_file)
binary_file_location = fixtures_project.send(:binary_file)
expect(binary_file_location).to eq("/FixtureScheme/FixtureApp.app/FixtureApp")
end

it "should return the binary file location for a framework bundle provided a scheme" do
allow(Dir).to receive(:[]).with("#{build_directory}/Build/Intermediates/CodeCoverage/FixtureScheme/*.framework").and_return(["/FixtureScheme/FixtureFramework.framework"])
it "should find the product path provided a scheme" do
fixtures_project.send(:configure_binary_file)
binary_file_location = fixtures_project.send(:binary_file)
expect(binary_file_location).to eq("/FixtureScheme/FixtureFramework.framework/FixtureFramework")
end

it "should return the binary file location for a test bundle provided a scheme" do
allow(Dir).to receive(:[]).with("#{build_directory}/Build/Intermediates/CodeCoverage/FixtureScheme/FixtureAppTests.xctest/**/FixtureAppTests").and_return(["/FixtureScheme/FixtureAppTests.xctest/Contents/MacOS/FixtureAppTests"])
fixtures_project.send(:configure_binary_file)
binary_file_location = fixtures_project.send(:binary_file)
expect(binary_file_location).to eq("/FixtureScheme/FixtureAppTests.xctest/Contents/MacOS/FixtureAppTests")
expect(binary_file_location).to end_with("Debug/libfixtures.a")
end

let(:fixture_yaml) do
Expand All @@ -184,26 +156,18 @@ class SpecXcode7CoverageFile < Slather::ProfdataCoverageFile

let(:other_fixture_yaml) do
yaml_text = <<-EOF
binary_basename: "FixtureFramework"
binary_basename: "fixtures"
EOF
yaml = YAML.load(yaml_text)
end

it "should configure the binary_basename from yml" do
allow(Slather::Project).to receive(:yml).and_return(other_fixture_yaml)
allow(Dir).to receive(:[]).with("#{build_directory}/Build/Intermediates/CodeCoverage/FixtureScheme/FixtureFramework.framework").and_return(["/FixtureScheme/FixtureFramework.framework"])
allow(Dir).to receive(:[]).with("#{build_directory}/Build/Intermediates/CodeCoverage/Products/Debug/fixtureTests.xctest").and_return(["fixtureTests.xctest"])
fixtures_project.send(:configure_binary_file)
binary_file_location = fixtures_project.send(:binary_file)
expect(binary_file_location).to eq("/FixtureScheme/FixtureFramework.framework/FixtureFramework")
expect(binary_file_location).to end_with("/fixturesTests.xctest/Contents/MacOS/fixturesTests")
end

# it "should find the binary file without any yml setting" do
# fixtures_project.configure_binary_file
# Dir.stub(:[]).with("#{build_directory}/Build/Intermediates/CodeCoverage/FixtureScheme/*.app").and_return(["/FixtureScheme/FixtureApp.app"])
# Dir.stub(:[]).with("/FixtureScheme/FixtureApp.app/**/FixtureApp").and_return(["/FixtureScheme/FixtureApp.app/FixtureApp"])
# binary_file_location = fixtures_project.send(:binary_file)
# expect(binary_file_location).to eq("/FixtureScheme/FixtureApp.app/FixtureApp")
# end
end

describe "#dedupe" do
Expand Down Expand Up @@ -449,8 +413,8 @@ class SpecXcode7CoverageFile < Slather::ProfdataCoverageFile

project_root = Pathname("./").realpath

["\nProcessing coverage file: #{project_root}/spec/DerivedData/libfixtures/Build/Intermediates/CodeCoverage/fixtures/Coverage.profdata",
"Against binary file: #{project_root}/spec/DerivedData/libfixtures/Build/Intermediates/CodeCoverage/fixtures/Products/Debug/fixturesTests.xctest/Contents/MacOS/fixturesTests\n\n"
["\nProcessing coverage file: #{project_root}/spec/DerivedData/libfixtures/Build/Intermediates/CodeCoverage/Coverage.profdata",
"Against binary file: #{project_root}/spec/DerivedData/libfixtures/Build/Intermediates/CodeCoverage/Products/Debug/fixturesTests.xctest/Contents/MacOS/fixturesTests\n\n"
].each do |line|
expect(fixtures_project).to receive(:puts).with(line)
end
Expand Down