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

Local cache directory? #31

Closed
danielbowden opened this issue Oct 19, 2020 · 13 comments
Closed

Local cache directory? #31

danielbowden opened this issue Oct 19, 2020 · 13 comments

Comments

@danielbowden
Copy link

Hi, great plugin! I have been using cocoapods-binary but just discovered this and it looks much more active.

I have a quick question. Our project currently uses git-lfs to store the large files/frameworks in our project.
Is it at all possible configure cocoapods-binary-cache to continue to use a local directory inside our repo instead of the separate git repo? Or even jut continue to use the _Prebuild directory? That way we do not have to fetch. We would like our project to continue to be able to be "cloned and run" without needing further steps, network, and user permissions to another repo.

I was hoping to maintain our workflow of just pre-building the desired pods and storing them locally as git-lfs automatically handles and uploads these large files.

I tried configuring the cache_repo config hash to just have a local key and not remote however did not have much luck.

Thanks,
Daniel

@trinhngocthuyen
Copy link
Contributor

Hi @danielbowden Thanks for reaching out!

I think this is an eligible request. It is also helpful for the integration tests of this repo as it currently depends on a personal git repo.

To minimize significant changes, here are my proposed changes:

  • The local cache is defined by the current param cache_repo -> default -> local, just like the way it currently is.
  • The local cache is taking effect when the param cache_repo -> default -> remote is not specified. In that case, any git action in the workflow (git fetch, git push) would be excluded.
  • The structure of a local cache would be the same as it currently is:
--- cache_dir / --- GeneratedFrameworks / --- FrameworkA.zip
            |                            |--- FrameworkB.zip
            |
            | --- Manifest.lock

We'll discuss this and proceed with the next step. Thanks!

@danielbowden
Copy link
Author

Hi @trinhngocthuyen, thanks for the quick reply!

That sounds like a solution that would work. For my understanding with this change how would you expect a typical workflow to go / commands to run for these scenarios:

  1. Adding a new pod dependency or updating the version of an existing dependency?
  2. Running a build from a fresh checkout on CI?

Would you still call fetch and push commands even though their source would be local? Or would you likely just use bundle exec pod binary prebuild and bundle exec pod install only after adding a new dependency as our pods are checked in?

I'm a big fan of Grab by the way! I've used it a lot in Malaysia, Singapore and Indonesia 🙂

@trinhngocthuyen
Copy link
Contributor

There are 2 main workflows using with this plugin:

  • (W1) Prebuild frameworks & deploy them to the cache store. (Let's use the term "cache store" to refer to the remote cache or local cache).
bundle exec pod binary prebuild [--push]
  • (W2) Install pods with the prebuilt frameworks from the cache store which were deployed in (W1).
bundle exec pod binary fetch # <-- Just need to run once, to unzip the cache to the prebuild sandbox `_Prebuild/`
bundle exec pod install

With the proposed change I mentioned earlier, the CLI for (W1) and (W2) remains the same.

  • The fetch command in (W2) will just unzip the cache from the cache store to the prebuild sanbox (aka. _Prebuild/ dir).
  • The push command won't do anything at all.
  • The prebuild command will prebuild frameworks, then zip them to the cache store. It's up to you to do the next step.

@danielbowden
Copy link
Author

Ok thanks for clarifying. Sounds like a good plan.

If I were to fetch which results in an unzip to the _Prebuild/ dir could I just continue to check in the _Prebuild dir to the repo instead of the cache store dir? I could make either option work for our project but just wondering if the following would work.

Developer 1's steps would be

  1. Add new pod or update existing pod in Podfile
  2. Run bundle exec pod binary prebuild
  3. Run bundle exec pod binary fetch
  4. Run bundle exec pod install
  5. Now commit the changed files in repo (new dependency, /Pods, _Prebuild, MainProject.xcodeproj, Pods.xcodeproj, Podfile, Podfile.lock, etc)

Now Developer 2 comes along, clones project and can just build and run? Without running any specific commands. Later when they want to contribute new dependencies or update existing ones they can learn the above workflow.

Thanks again for your help, and the efforts put into the plugin.

@trinhngocthuyen
Copy link
Contributor

If I were to fetch which results in an unzip to the _Prebuild/ dir could I just continue to check in the _Prebuild dir to the repo instead of the cache store dir?

With the scenario you mentioned, you just don't need to care about the cache store despite its existence :).

Aslso, by default, the prebuild sandbox _Prebuild/ is within the same repo as your project. And you can specify this path to be outside or inside the current repo with the prebuild_sandbox_path option. Kindly refer to Configure cocoapods-binary-cache
...

  • The workflow of dev 2 would work as expected (ie. he/she only needs to run bundle exec pod install)
  • If you prefer _Prebuild/ to the the source of truth instead of the cache store, it would be more convenient if we add the --no-fetch option to the prebuild command. That way, the _Prebuild/ sandbox won't be overwritten by the unzip action if we execute the prebuild command.

@trinhngocthuyen
Copy link
Contributor

trinhngocthuyen commented Oct 19, 2020

To summarize, the change needed in the plugin is:

  • Support: cache store as local only. (when the cache_repo -> default -> remote is absent)
  • A --no-fetch option to the CLI of the prebuild command.

@danielbowden
Copy link
Author

Thanks @trinhngocthuyen those changes sound like they would cover it.
With _Prebuild as the source of truth the --no-fetch option looks like it could be helpful.

You got me thinking about your workflow. With a remote cache (or even a local cache moving forward) What do you checkin (if anything) to source control from your main project's Pods directory?

And in the case of moving from Xcode 11.7 to Xcode 12/Swift 5.3 do you do a complete rebuild of all your frameworks as part of your Xcode migration and push up the new prebuilt files to replace the old?

@trinhngocthuyen
Copy link
Contributor

trinhngocthuyen commented Oct 20, 2020

Following is our setup:

  • Anything related to Pods or _Prebuilt/_Prebuild is not tracked by git in the project repo.
  • The cache is tracked in a separate cache repo.
  • We run scheduled jobs on CI to continuously deploy cache to the cache repo.
  • In case of migrating to Xcode 12, everything should work normally because:
    • We added the SWIFT_VERSION to the cache validation (can configured by validate_prebuilt_settings option). Frameworks previously built with Xcode 11 have different SWIFT_VERSION and will be treated as cache miss. They will be integrated as normal (ie. with sources)
    • Then the scheduled jobs above will re-deploy new cache with Xcode 12.
    • Alternative: We do support multi cache repos. You can use different repos for cache deployed with Xcode 11 & cache deployed with Xcode 12

@trinhngocthuyen
Copy link
Contributor

Hi @danielbowden, kindly check out version 0.1.11 for this support.

Sample configuration:

config_cocoapods_binary_cache(
  cache_repo: {
    "default" => {
      "local" => "<path/to/cache/dir>" # <--- HERE
    }
  }
)

Thanks!

@danielbowden
Copy link
Author

danielbowden commented Nov 17, 2020

Hi @trinhngocthuyen thank you for following up with this feature so quickly, local only support is great and I hope others benefit from it too. I've had a look at the changes and I'm keen to give it a try.

I have just tried to integrate it with our project but ran into a few issues. I could very much be using it incorrectly though I apologise.

  1. Existing project and Podfile. I installed the gem, added the config to my Podfile and added the :binary => true flag to the dependencies I wanted to prebuild.

  2. Ran bundle exec pod binary prebuild and the plugin identified my dependencies, had a cache miss and then proceed to install and compile each dependency.

  3. The pre build failed for Realm. This may be an issue with Realm not the plugin, but it does build successfully without the plugin. Realm is also our slowest/biggest dependency in our pipeline so it would be great to get this one cached.

> Copying /Users/danielbowden/myprojectxxx/_Prebuild/Realm/include/RLMCollection.h
> Copying /Users/danielbowden/myprojectxxx/_Prebuild/Realm/include/RLMArray.h
> Copying /Users/danielbowden/myprojectxxx/_Prebuild/Realm/include/NSError+RLMSync.h
> Compiling work_queue.cpp
> Compiling uuid.cpp

[x] /Users/danielbowden/myprojectxxx/_Prebuild/Realm/Realm/ObjectStore/src/util/uuid.cpp:19:10: 'util/uuid.hpp' file not found

#include "util/uuid.hpp"
         ^~~~~~~~~~~~~~~


> Compiling weak_realm_notifier.cpp

[x] /Users/danielbowden/myprojectxxx/_Prebuild/Realm/Realm/ObjectStore/src/impl/weak_realm_notifier.cpp:19:10: 'impl/weak_realm_notifier.hpp' file not found

#include "impl/weak_realm_notifier.hpp"
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


> Compiling transact_log_handler.cpp

[x] /Users/danielbowden/myprojectxxx/_Prebuild/Realm/Realm/ObjectStore/src/impl/transact_log_handler.cpp:19:10: 'impl/transact_log_handler.hpp' file not found

#include "impl/transact_log_handler.hpp"
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


> Compiling thread_safe_reference.cpp

[x] /Users/danielbowden/myprojectxxx/_Prebuild/Realm/Realm/ObjectStore/src/thread_safe_reference.cpp:19:10: 'thread_safe_reference.hpp' file not found

#include "thread_safe_reference.hpp"
         ^~~~~~~~~~~~~~~~~~~~~~~~~~~


> Compiling system_configuration.cpp
> Compiling sync_user.cpp
> Compiling sync_session.cpp
...
### Error
RuntimeError - Fail to build targets
  1. I switched Realm to:binary => false so I could see how it behaved with my other dependencies. I also tried no flag at all.

  2. I ran bundle exec pod binary prebuild again and it finished successfully. I now see files in my local cache dir (eg. /local/cache/dir/GeneratedFrameworks/AppAuth.zip). However inside these zips are just two files AppAuth.pod_name 0bytes and metadata.json. I would've expected to see a .framework file here? I can see several changes in git to my Xcode project like removing the frameworks from the Cocoapods embed build phase:
    "${BUILT_PRODUCTS_DIR}/AppAuth/AppAuth.framework",
    "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppAuth.framework".
    So now if I build I get a "No such module 'AppAuth'" error.

  3. I reverted my changes and instead just added :binary => true to one dependency to see if I could get it working just for that one. I observed the same as in 5. and also noticed it removed that dependency from the FRAMEWORK_SEARCH_PATHS entry in the Pods xcconfig file. Building fails with the same No such module.

I'm sorry, am I missing a step in getting this working? This doesn't seem like it would be related to your local cache change.

On a side note, I noticed in another issue reply that you only build for Debug config to speed up development and do not use this plugin when building for Release. Does that mean it is only possible to specify one prebuild_config:? I saw you use a IS_POD_BINARY_CACHE_ENABLED env var, I assume you do not set this for your release builds?

@trinhngocthuyen
Copy link
Contributor

Hi @danielbowden, Thank you for the detailed report & for trying out many scenarios to see the pattern. We really appreciate it.
I think it would be better if we can track the issue in another thread as it's not quite relevant to the original issue.

From what you reported, there are two highlighted issues:

  • (I1). Cannot prebuild Realm.
  • (I2). Cannot even prebuild with other modules as well such as AppAuth.

This is what I tried on a demo project (kindly refer to https://github.com/trinhngocthuyen/troubleshoot-pod-bin-cache/tree/78479e5/issue-31)

  • For (I2), I can prebuild AppAuth successfully and use the prebuilt frameworks without any problem. Kindly check out the demo project.

    • My guess for (I2) is: the prebuild sandbox _Prebuild was not in the correct state when switching the :binary option back and forth. In such cases, you should remove _Prebuild and Pods directories.
  • For (I1), I can prebuild Realm successfully. However, when building the project with the prebuilt frameworks, it throws an error include of non-modular header inside framework module 'Realm', just like I have problem when building  #24 (comment).
    --> For this, I'll look into it. In the meantime, please try out some suggestions in I have problem when building  #24 (comment) to see if it resolves the issue. In the worst case, please temporarily exclude Realm and RealmSwift from the plugin until we have figure out the root cause :(

Thank you!

@danielbowden
Copy link
Author

Hi @trinhngocthuyen,

Thanks so much for the thorough troubleshooting and the example project.

Interestingly I grabbed your example project and ran your run_me.sh script and it worked and the project workspace built fine. I also flipped the RealmSwift binary flag to true and ran run_me.sh again and surprisingly that worked too!? I left the CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES build setting commented out so its not that either.

In that example project I now I have 3 generated frameworks (AppAuth, Realm and RealmSwift) in _Prebuild and local cache. So I'm not sure why we both experienced problems with Realm before?

This is running:
Xcode 12.0.1
Cocoapods 1.10.0
Cocoapods Binary Cache (0.1.11)

I will give it another go with our proper project and see how it goes.

One last question, is it possible to do something like this (or similar syntax)?

config_cocoapods_binary_cache(
  cache_repo: {
    "default" => {
      "local" => "Support/cocoapods-binary-cache-debug",
      "prebuild_config" => "Debug"
    },
    "release" => {
      "local" => "Support/cocoapods-binary-cache-release",
      "prebuild_config" => "Release"
    },
  }
)

I noticed in another issue reply that you only build for Debug config to speed up development and do not use this plugin when building for Release. I saw you use a IS_POD_BINARY_CACHE_ENABLED env var, I assume you do not set this for your release builds?

@trinhngocthuyen
Copy link
Contributor

1/ As per my testing with Realm, the issue about non-modular header import happens with Xcode 11. Although it's working fine with Xcode 12, I haven't really understood the root cause and hence suggested a workaround for Xcode prior to 12.0.

2/ About the syntax of prebuild_config:
Currently, it's not inside the cache_repo option. So, right now the practice for using different prebuild_config is to dynamically change the variable.

config_cocoapods_binary_cache(
  ...
  prebuild_config: ENV["PREBUILD_CONFIG"] || "Debug"
)

However, this structure you suggested makes much sense as it fits the use cases of multiple cache storage.
--> We'll think about that.

3/ About whether to use this plugin for Release for not:
This plugin particularly solves the project build time problem which is the bottleneck of testing on CI and local development. For CD, this seems not to be a big deal. In fact, we are very cautious about reliability when it comes to Release builds. Therefore, we don't enable this plugin when shipping QA/production builds.

Anw, it's up to you to design the setup that fits the project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants