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
Automatically change dynamic frameworks to static? #2575
Comments
@dimazen The Mach-O type of your project should be "static library". Carthage doesn't turn a dynamic library into a static one for you. |
I have similiar question. @dimazen answer for how to build statically with carthage is in my question |
@dannydaddy3 actually I thought that Carthage has a way to build dynamic frameworks as a static just as StaticFrameworks does. And ideally it would be automatic. However this is not the case, since we need dependency to support this way of distribution. This is ok, but it requires some additional automation for me. |
@dimazen and @dannydaddy3 you're both correct, it's not automatic. If you want to avoid the Mach-O types every time, just fork the projects you want to be static and use your forks. If you have a proposal on how an automatic process would look like, feel free to to submit it here and we'll discuss it. |
@blender I currently doesn't have any code to show, but from what I understood: not all of the frameworks can be linked as a static ones. For example if framework uses resources and a NSBundle API, linking it statically will mess things up. Preferred way would be to put an additional attribute in the Cartfile like |
@dimazen @blender Yes, forks is a good option, but has some overhead with updating dependencies. It would be super convenient if we could mark somehow those frameworks to be build statically in Cartfile. And after pulling dependencies Carthage would update Mach-O type in build setting for marked ones. |
Sorry, I don't think we want to touch the Cartfile. I was more thinking of a CLI switch but that works only if build one framework at the time with Carthage |
@blender Maybe there is a way to start adding attributes to the Cartfile? |
I understand, but so far any change to the Cartfile is a no go. It might change in the future. |
@blender It seems to me that we somehow and somewhere has to instruct Carthage about what we want to be static. If we couldn't do this in Cartfile for now, may be it could be separate file? |
I am afraid that is not an option either but let's consult @mdiep I think the best way it to just fork the repo and update the Mach-O type. |
Ok, got you. Generally it's just seems to be an important feature that majority of Carthage users would make use of. Hope to see this implemented in future. |
I would really interested to know more on why we can't introduce either attributes or a separate file for static dependencies. Maintaining multiple forks when it comes to an update would be really time consuming. Therefore waiting for @mdiep response. |
as I suggested in another issue:
should ease the pain of forking and you can still use the regular repos |
Damn, that looks promising as for me. I'll try to take a look at what can be done by script. Maybe good implementation can be included into a Carthage |
Has anyone succeeded integrating static framework using this approach with Xcode 10? I've patched the project setting Mach-O to "static library", but dyld can't find the "matching architecture" when I try to Run the app via Xcode:
while
I added the Kingfisher.framework into "Link Binary With Libraries" and "Embed Frameworks" steps. If I don't add it to "Embed Frameworks" step, the linker emits this error
|
As far as I can tell you are linking it dynamically and not statically. You do no need the embed step. All you should have to do is literally drag and drop. |
@blender you were right, looks like some previous build leftovers were messing things up, after all. Thanks again for your help, managed to make it work. |
We currently use these two scripts: build_frameworks.sh
patch_statically_linked_project.rb
It doesn't support all the libraries (some libraries use xcworkspace, others just don't work for some reason), but we were able to port a few. |
yes thank you! |
I've tried to automate process a little bit. So far there is a ruby script which has to be invoked from the root of the project folder (just as we do for Carthage). #!/usr/bin/env ruby
require 'xcodeproj'
require 'yaml'
require 'set'
class IgnoreEntry
attr_reader :dependency
@targets
def initialize(object)
if object.instance_of? String then
@dependency = object
@targets = Set.new
elsif object.is_a? Hash then
dependency, targets = object.first
@dependency = dependency
@targets = Set.new(targets.flat_map(&:values).flatten)
else
@dependency = nil
@targets = nil
raise "Unexpected ignoreMap format"
end
end
def should_ignore?
@targets.empty?
end
def should_ignore_target?(target)
return @targets.empty? || @targets.member?(target)
end
end
def find_resolved_deps
raw_deps = File.read(File.join(Dir.pwd, "Cartfile")).split("\n")
regexp = %r{"(?<repo>[\w\d@\:\-\_\.\/]+)"?}
raw_deps.map { |x|
match = regexp.match(x)
repo = match[:repo] unless match.nil?
if not repo.nil? then
repo = %x[read -a array <<< '#{repo}'; basename ${array[0]} | awk -F '.' '{print $1}']
repo.chop
end
}.compact
end
def xcproject_from_workspace_at_path(dependency, directory)
workspace_path = File.join(directory, "#{dependency}.xcworkspace")
return unless File.exists?(workspace_path)
workspace = Xcodeproj::Workspace.new_from_xcworkspace(workspace_path)
project_ref = workspace.file_references.detect { |ref|
ref if File.basename(ref.path, File.extname(ref.path)) == dependency
}
return nil if project_ref.nil?
project_path = project_ref.absolute_path(directory)
Xcodeproj::Project.open(project_path)
end
def xcodeproj_for_dependency(dependency, directory = "#{Dir.pwd}/Carthage/Checkouts/#{dependency}")
project_path = Dir.chdir(directory) { |pwd|
candidates = ["#{dependency}.xcodeproj"] + Dir.glob("*.xcodeproj")
candidates.map { |name|
File.join(pwd, name)
}.detect { |path|
File.exists?(path)
}
}
return xcproject_from_workspace_at_path(dependency, directory) if project_path.nil?
Xcodeproj::Project.open(project_path)
end
def patch_match_o_type(dependency, ignore_entry)
if !ignore_entry.nil? && ignore_entry.should_ignore? then
puts "Skipping dependency <#{dependency}>. Ignored by Patchfile"
return
end
project = xcodeproj_for_dependency(dependency)
project.native_targets.each do |target|
if target.product_type != 'com.apple.product-type.framework' || target.resources_build_phase.files.count > 0 then
next
end
target.build_configurations.each do |config|
if not ignore_entry.nil? and ignore_entry.should_ignore_target?(target.name) then
puts "Skipping target <#{target.name}, #{config.name}>. Ignored by Patchfile"
next
end
match_o_type = config.build_settings['MACH_O_TYPE']
if match_o_type.nil? || match_o_type == 'mh_dylib' then
puts "Patching target <#{target.name} #{config.name}>"
config.build_settings['MACH_O_TYPE'] = 'staticlib'
project.save
else
puts "Skipping target <#{target.name} #{config.name}>. Already has MACH_O_TYPE set to <#{match_o_type}>"
end
end
end
end
def prepare_ignore_hash
config_path = File.join(Dir.pwd, "Patchfile")
config = File.file?(config_path) ? YAML.load_file(config_path) : {}
raw_ignore_map = config.fetch("ignoreMap", {})
ignore_map = {}
if raw_ignore_map.count > 0 then
ignore_map = raw_ignore_map.map { |x|
entry = IgnoreEntry.new(x)
[entry.dependency, entry]
}.to_h
end
ignore_map
end
ignore_hash = prepare_ignore_hash
resolved_deps = find_resolved_deps
resolved_deps.each { |dependency|
puts "Processing dependency <#{dependency}>"
patch_match_o_type(dependency, ignore_hash[dependency])
puts "\n"
} You can also skip unused targets by creating ignoreMap:
- Quick:
- target: Quick-iOS
- Nimble:
- targets: [Nimble-iOS, Nimble-macOS]
- SVProgressHUD |
@dimazen shall we add this to https://github.com/Carthage/workflows ? |
@blender I would first appreciate some feedback from participants. Maybe some of them will find issues, etc. (works ok on my production project with various dependencies but who knows). i.e. this script needs some polishing Anyway, I'll keep on iterating during the week and then get back to you. |
@dimazen Hi, thanks for making this script! I tried it with my project setup, but I have issues with transitive dependencies. In this setup, I declare a dependency to RxCoreData, which depends on RxSwift. RxCoreData is built as static, but not RxSwift.
If I add RxSwift to my own dependencies, it will be patched to static, but the build will fail because RxCoreData has set its frameworks search path to Carthage/Build/iOS/:
|
Hello, @MaximeLM. Thanks for your feedback. |
@MaximeLM ok, so far here is my setup: Add
In the
Make a clean build of those 2 dependencies (just throw away build artifacts for them and start a new build: Then you will have to link static libraries into your app target + link RxSwift and add only RxSwift.framework to Also this static linking may cause symbols duplication. Lets imagine RxCoreData and RxCocoa both get linked against RxSwift. Later you're adding RxCoreData, RxCocoa and RxSwift into your app. This setup will result into 3 copies of RxSwift in your binary and gonna confuse linker. Therefore you need to be careful to not duplicate dependencies which requires some manual adjustments. |
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. |
@dimazen Thanks for this script! I've found it very helpful. I also think this would be a great feature to support out-of-the-box in Carthage. I think it's untenable to maintain forks of all of a project's dependencies just to patch one flag, so it would be helpful to have building-in tooling to patch this post-checkout. |
Hello!
Latest release includes support for static library. Also README says about it. However, there is no info about which flag needs to be passed to Carthage to output a static framework. I've been looking in sources of Carthage but with no luck so far.
The text was updated successfully, but these errors were encountered: