diff --git a/.travis.yml b/.travis.yml index 37d2b080..cbfa3efd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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. diff --git a/CHANGELOG.md b/CHANGELOG.md index 145707c3..a19e9612 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/lib/slather/project.rb b/lib/slather/project.rb index 990a9503..333584a7 100644 --- a/lib/slather/project.rb +++ b/lib/slather/project.rb @@ -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 @@ -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 @@ -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 diff --git a/spec/slather/project_spec.rb b/spec/slather/project_spec.rb index ca3f5ddd..fec7bc22 100644 --- a/spec/slather/project_spec.rb +++ b/spec/slather/project_spec.rb @@ -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 @@ -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 @@ -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 @@ -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