2

I want to add onlyGenerateCoverageForSpecifiedTargets property to TestAction object programmatically. According to the documentation this property is not yet supported. So I need to add a custom property to an object. Also I need to add CodeCoverageTargets group. Here is my code:

scheme = Xcodeproj::XCScheme.new
scheme.add_build_target(app_target)
scheme.set_launch_target(app_target)
scheme.add_test_target(target)

test_action = scheme.test_action
test_action.code_coverage_enabled = true

# add onlyGenerateCoverageForSpecifiedTargets = true

scheme.test_action = test_action
scheme.save_as(xcode_proj_dir, name)

Here is xml structure when I add property from Xcode GUI.

   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES"
      codeCoverageEnabled = "YES"
      onlyGenerateCoverageForSpecifiedTargets = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "D7CE66BC1C7DE6F700FC64CC"
            BuildableName = "AppName.app"
            BlueprintName = "AppName"
            ReferencedContainer = "container:buddyui.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
      <CodeCoverageTargets>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "D7CE66BC1C7DE6F700FC64CC"
            BuildableName = "AppName.app"
            BlueprintName = "AppName"
            ReferencedContainer = "container:buddyui.xcodeproj">
         </BuildableReference>
      </CodeCoverageTargets>
ChikabuZ
  • 10,031
  • 5
  • 63
  • 86
  • 1
    Please post code, errors, sample data or textual output here as plain-text, not as images that can be hard to read, can’t be copy-pasted to help test code or use in answers, and are barrier to those who depend on screen readers or translation tools. You can edit your question to add the code in the body of your question. For easy formatting use the `{}` button to mark blocks of code, or indent with four spaces for the same effect. The contents of a **screenshot can’t be searched, run as code, or copied and edited to create a solution.** – tadman Mar 20 '21 at 07:44

2 Answers2

1

I'll say it first: I know nothing about the Xcodeproj Gem nor the logic behind Xcode metadata. Take my code as a starter for further improvements.

You have a few ways of achieving what you asked:

  1. MonkeyPatch Xcodeproj. That is what I did, sorry for that :-P

  2. Extend Xcodeproj classes. That would be the recommended solution.

  3. Manipulate the XML file or the XCScheme object directly, with REXML.

Here comes my proposal. I added a few methods to TestAction (based on the code of similar existing methods) and created the additional class CodeCoverageTargets (based on the class MacroExpansion). As I don't know how Xcode works, I chose to create the method add_code_coverage_targets in XCScheme instead of overwriting set_launch_target (where MacroExpansion is instantiated).

require 'xcodeproj'

class Xcodeproj::XCScheme

  def add_code_coverage_targets(build_target)
    code_cov_targets = CodeCoverageTargets.new(build_target)
    test_action.add_code_coverage_targets(code_cov_targets)
  end

  class CodeCoverageTargets < XMLElementWrapper
    def initialize(target_or_node = nil)
      create_xml_element_with_fallback(target_or_node, 'CodeCoverageTargets') do
        self.buildable_reference = BuildableReference.new(target_or_node) if target_or_node
      end
    end
    def buildable_reference
      @buildable_reference ||= BuildableReference.new @xml_element.elements['BuildableReference']
    end
    def buildable_reference=(ref)
      @xml_element.delete_element('BuildableReference')
      @xml_element.add_element(ref.xml_element)
      @buildable_reference = ref
    end
  end

  class TestAction
    def only_generate_coverage_for_specified_targets?
      string_to_bool(@xml_element.attributes['onlyGenerateCoverageForSpecifiedTargets'])
    end
    def only_generate_coverage_for_specified_targets=(flag)
      @xml_element.attributes['onlyGenerateCoverageForSpecifiedTargets'] = bool_to_string(flag)
    end
    def code_coverage_targets
      @xml_element.get_elements('CodeCoverageTargets').map do |node|
        CodeCoverageTargets.new(node)
      end
    end
    def add_code_coverage_targets(code_coverage_targets)
      @xml_element.add_element(code_coverage_targets.xml_element)
    end

  end
end

You can use it like this:

xcode_proj_dir = 'Desktop/SO/66719313/DummyApp.xcodeproj'
xcode_proj = Xcodeproj::Project.open(xcode_proj_dir)
app_target = xcode_proj.targets.first

scheme = Xcodeproj::XCScheme.new
scheme.add_build_target(app_target)
scheme.set_launch_target(app_target)
#scheme.add_test_target(app_target)
scheme.add_code_coverage_targets(app_target) # new method

test_action = scheme.test_action
test_action.code_coverage_enabled = true
test_action.only_generate_coverage_for_specified_targets = true # new method
puts test_action
Fravadona
  • 13,917
  • 1
  • 23
  • 35
0

You can simply create a module which adds the behaviour you want (i.e. your method), but I'm not sure this will fix your real problem

module AddOnlyGenerateCoverageForSpecifiedTargets
  attr_accessor :only_generate_coverage_for_specified_targets 
end

Then include the module in the class you want it. To include it in one object only, include it in its singleton_class:

test_action.singleton_class.include AddOnlyGenerateCoverageForSpecifiedTargets

# Now you can use it
test_action.only_generate_coverage_for_specified_targets = true
Jaffa
  • 12,442
  • 4
  • 49
  • 101
  • Unfortunatelly it donsn't work. There is no `onlyGenerateCoverageForSpecifiedTargets ` in .xcscheme file. – ChikabuZ Mar 22 '21 at 10:44
  • @ChikabuZ you'll need to change the way it's serialized to add the value to the output – Jaffa Mar 22 '21 at 10:50