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

Automate `copy-frameworks` command? #2605

Open
dimazen opened this Issue Oct 5, 2018 · 8 comments

Comments

Projects
None yet
3 participants
@dimazen
Copy link
Contributor

dimazen commented Oct 5, 2018

Hello Carthage team!

As you know in order to get things done we need to invoke copy-frameworks at the Build Phase. This also involves specifying a list of input files for each linked framework. This seems to be a good subject for an automation. Currently it might be a little annoying to keep this list in sync and to manually fill it in. This is the main motivation.

From my attempt to make a draft of such functionality here is what I found:
Initially we need to grab list of the linked frameworks that was built by Carthage. Turns out that they are stored in the Frameworks Build Phase. Unfortunately built-in tool xcodebuild doesn't allow us to read such information, therefore we used to read contents of the .xcodeproj itself. There are well-known tools such as gem xcodeproj and its Swift counterpart xcodeproj.

First of all we're reading contents of the Frameworks Build Phase. The rest of the process is very simple: select only those frameworks that are located in Carthage/Build but not in the Static subfolder.
Then we simply iterating over found frameworks and filling in env variables like SCRIPT_INPUT_FILE_ and so on.

Below you can find working draft in ruby:

#!/usr/bin/env ruby

require 'xcodeproj'

# We need to find linked frameworks that are located in the `Carthage/Build/*` folder
# but also doesn't appear to be static.
# Focus on the linked frameworks prevents us from accidental copying of the
# frameworks that belong to a different target (e.g. UnitTests).
def find_linked_frameworks_refs
  # These env variables passed by Xcode
  # during invocation of the Run Script Build Phase
  # Therefore running script on its own will have no effect.
  project_file_path = ENV["PROJECT_FILE_PATH"]
  target_name = ENV["TARGET_NAME"]

  return [] if project_file_path.nil? || target_name.nil?

  project = Xcodeproj::Project.open(project_file_path)
  target = project.targets.detect { |target| target.name == target_name }

  return [] if target.nil?

  refs = target.frameworks_build_phases.files_references

  # Selecting only Dynamic Framworks built by Carthage. 
  # Static frameworks also appears in this list but we don't need to process them.
  regexp = %r{^Carthage\/Build\/((?!\/Static\/).)*$}
  refs.select do |ref|
    regexp.match(ref.path) != nil
  end
end

def export_io_vars_for_refs(refs)
  # The same: env vars exported by Xcode. Manual invocation will have no effect.
  srcroot = ENV["SRCROOT"]
  build_products_directory = ENV["BUILT_PRODUCTS_DIR"]
  frameworks_folder_path = ENV["FRAMEWORKS_FOLDER_PATH"]

  return if srcroot.nil? || build_products_directory.nil? || frameworks_folder_path.nil?

  refs.each_with_index do |ref, index|
    # Ref has a relative path. We assume that Carthage folder located in the $SRCROOT.
    input_path = File.join(srcroot, ref.path)
    # Exporting variables for `carthage copy-frameworks`.
    ENV["SCRIPT_INPUT_FILE_#{index}"] = input_path

    # Specify output files to speed up execution.
    # My concern at this point is that I'm not sure whether specifying these
    # vars within script itself actually have any impact on Xcode.
    # It might be the case when Xcode evaluates equality of the input/output
    # files prior to run of the script.
    output_path = File.join(build_products_directory, frameworks_folder_path, File.basename(ref.path))
    ENV["SCRIPT_OUTPUT_FILE_#{index}"] = output_path
  end

  ENV["SCRIPT_INPUT_FILE_COUNT"] = refs.count.to_s
  ENV["SCRIPT_OUTPUT_FILE_COUNT"] = refs.count.to_s
end

refs = find_linked_frameworks_refs
if refs.count > 0 then
  export_io_vars_for_refs(refs)
  exec "/usr/local/bin/carthage copy-frameworks"
end

Simply put it to the new build phase and invoke as follow:

ruby ${SRCROOT}/copy-frameworks.rb

Important: script requires Xcodeproj gem. Therefore is case you're managing ruby via rbenv, please use following script:

export PATH=~/.rbenv/shims:$PATH
ruby ${SRCROOT}/copy-frameworks.rb

The only downside at this point is that this automation would require from Carthage to add one more dependency (ruby gem or swift counterpart).

UPD:
Alternatives considered:
Simply copy contents of the Carthage/Build/... to the destination.
Pros: removes extra dependency
Cons: copies frameworks from other targets (if any) to the destination target.

@blender

This comment has been minimized.

Copy link
Member

blender commented Oct 5, 2018

Hi @dimazen

The rest of the process is very simple: select only those frameworks that are located in Carthage/Build but not in the Static subfolder.

Why not just iterate over Carthage/Buildto build the list ignoring the project file? Do I miss something?

@dimazen

This comment has been minimized.

Copy link
Contributor Author

dimazen commented Oct 5, 2018

Hi, @blender !
Yes, I've commented this in the script itself. We can't just copy all of the values. Lets say in my main target I have Realm and in my UnitTests I have a Quick + Nimble. Simply iterating over the build artefacts will eventually copy Quick and Nimble to the main target which is something we don't want to :)

@blender

This comment has been minimized.

Copy link
Member

blender commented Oct 5, 2018

Ah! Right, good point.

@dimazen

This comment has been minimized.

Copy link
Contributor Author

dimazen commented Oct 5, 2018

Yeah, in the end if team would accept extra dependency, I can then update existing copy-frameworks to a newer version (as a PR) by adding extra option (--automatic?) to keep it backward-compatible.

@blender

This comment has been minimized.

Copy link
Member

blender commented Oct 5, 2018

Maybe you want to take a look at this issue? #2477

@bwhiteley

This comment has been minimized.

Copy link
Contributor

bwhiteley commented Oct 5, 2018

@dimazen we wrote a tool to do just this, as well as other Carthage-related tasks.
https://github.com/Ancestry/carttool

From the ReadMe:
"In place of the standard carthage copy-frameworks Run Script Build Phase, add a Build Phase with the script carttool copy-carthage-frameworks. You can omit the Input Files and Output Files. Just make sure the frameworks listed in Linked Frameworks and Libraries are correct. Note that you have to include all frameworks, including transitive dependencies in Linked Frameworks and Libraries for this to work. Do not include Carthage-built frameworks in Embedded Binaries.

"carttool copy-carthage-frameworks will first look for frameworks in Derived Data, and then fall back to looking for frameworks under Carthage/Build/<platform>. This facilitates switching dependencies to "development mode". If you include a dependency in a workspace with your current project, the framework built from the workspace will be used instead of the Carthage-built framework."

The Carthage team has been reluctant to read Xcode project files (as mentioned in #2477). To eliminate the need to read the project file, and eliminate the dependency on xcproj, one could use otool -L to figure out which frameworks are really needed. This would have the added benefit that only direct dependencies (not transitive dependencies) would have to be listed in Linked Frameworks and Libraries.

One advantage of the "Input Files"/"Output Files" mechanism built in to Xcode is that it will check timestamps of the input and output files and do nothing if the output file is already up-to-date. Ideally a tool that automatically sets the SCRIPT_INPUT_FILE_... and SCRIPT_OUTPUT_FILE_... environment variables could similarly check the timestamps and omit frameworks that are already up-to-date.

@dimazen

This comment has been minimized.

Copy link
Contributor Author

dimazen commented Oct 5, 2018

@bwhiteley wow, your tool looks solid 🚀. Haven't heard of it before.
I wish at least partially it to be included into the Carthage. Functionality of automatic dependencies verification as well as copying is really a must have.

Let see what Carthage team will decide with 3rd party dependencies for .xcodeproj ready.

@stale

This comment has been minimized.

Copy link

stale bot commented Nov 4, 2018

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Nov 4, 2018

@stale stale bot closed this Nov 11, 2018

@blender blender removed the stale label Mar 13, 2019

@blender blender reopened this Mar 13, 2019

@blender blender added the enhancement label Mar 13, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.