3434Usage:
3535# Dryrun (does not update any files on disk but prints out all replacements for
3636# preview) - Update versions in default set of files in the repository.
37- python3 scripts/update_ios_android_dependencies .py --dryrun
37+ python3 scripts/update_android_ios_dependencies .py --dryrun
3838
39- # Update versions in default set of files in the repository
40- python3 scripts/update_ios_android_dependencies.py
39+ # Update versions in default set of files in the repository.
40+ python3 scripts/update_android_ios_dependencies.py
41+
42+ # Update versions in default set of files in the repository. (allow updating to
43+ # experimental versions)
44+ python3 scripts/update_android_ios_dependencies.py --allow_experimental
4145
4246# Update only Android packages
43- python3 scripts/update_ios_android_dependencies .py --skip_ios
47+ python3 scripts/update_android_ios_dependencies .py --skip_ios
4448
4549# Update specific pod files (or directories containing pod files)
46- python3 scripts/update_ios_android_dependencies .py --podfiles foo/Podfile
50+ python3 scripts/update_android_ios_dependencies .py --podfiles foo/Podfile
4751 dir_with_podfiles
4852
53+ # Update all Android packages except any names containing androidx and auth
54+ python3 scripts/update_android_ios_dependencies.py --skip_ios
55+ --ignore_android_packages auth androidx
56+
57+ # Ignore updating any files in gameloop_android and test directories
58+ python3 scripts/update_android_ios_dependencies.py --ignore_directories test
59+ gameloop_android
60+
4961Other similar flags:
5062--depfiles
5163--readmefiles
6678import tempfile
6779
6880from collections import defaultdict
69- from pkg_resources import packaging
7081from xml .etree import ElementTree
82+ from pkg_resources import packaging
7183
7284
7385def get_files_from_directory (dirpath , file_extension , file_name = None ,
74- absolute_paths = True ):
86+ absolute_paths = True , ignore_directories = None ):
7587 """Helper function to filter files in directories.
7688
7789 Args:
@@ -83,27 +95,35 @@ def get_files_from_directory(dirpath, file_extension, file_name=None,
8395 absolute_paths (bool, optional): Return absolute paths to files.
8496 Defaults to True.
8597 If False, just filenames are returned.
98+ ignore_directories (list[str], optional): Directory names to ignore.
99+ Eg: ['gameloop_android', 'do_not_scan_this_directory']
86100
87101 Returns:
88102 list(str): List of files matching the specified criteria.
89103 List of filenames (if absolute_paths=False), or
90104 a list of absolute paths (if absolute_paths=True)
91105 """
106+ if ignore_directories is None :
107+ ignore_directories = []
108+
92109 files = []
93- for dirpath , _ , filenames in os .walk (dirpath ):
110+ for root , dirs , filenames in os .walk (dirpath ):
111+ dirs [:] = [directory for directory in dirs
112+ if directory not in ignore_directories ]
94113 for filename in filenames :
95114 if not filename .endswith (file_extension ):
96115 continue
97116 if file_name and not file_name == filename :
98117 continue
99118 if absolute_paths :
100- files .append (os .path .join (dirpath , filename ))
119+ files .append (os .path .join (root , filename ))
101120 else :
102121 files .append (filename )
103122 return files
104123
105124
106- def get_files (dirs_and_files , file_extension , file_name = None ):
125+ def get_files (dirs_and_files , file_extension , file_name = None ,
126+ ignore_directories = None ):
107127 """Gets the final flat list of files after searching directories.
108128
109129 If a directory is passed, it is searched recursively.
@@ -115,6 +135,8 @@ def get_files(dirs_and_files, file_extension, file_name=None):
115135 Eg: '.gradle'
116136 file_name (str, optional): Exact file name to search for.
117137 Defaults to None. Eg: 'foo.gradle'
138+ ignore_directories (list[str], optional): Directory names to ignore.
139+ Eg: ['gameloop_android', 'do_not_scan_this_directory']
118140
119141 Returns:
120142 iterable(str): Final list of files after recursively searching dirs.
@@ -125,13 +147,17 @@ def get_files(dirs_and_files, file_extension, file_name=None):
125147 if not os .path .exists (abspath ):
126148 continue
127149 if os .path .isdir (abspath ):
128- files = files + get_files_from_directory (abspath ,
129- file_extension = file_extension ,
130- file_name = file_name )
150+ files = files + get_files_from_directory (
151+ abspath ,
152+ file_extension = file_extension ,
153+ file_name = file_name ,
154+ ignore_directories = ignore_directories )
131155 elif os .path .isfile (abspath ):
132156 files .append (abspath )
133157 return files
134158
159+ # Regex to match versions with just digits (ignoring things like -alpha, -beta)
160+ RE_NON_EXPERIMENTAL_VERSION = re .compile ('[0-9.]+$' )
135161
136162########## iOS pods versions update #######################################
137163
@@ -158,47 +184,73 @@ def get_files(dirs_and_files, file_extension, file_name=None):
158184)
159185
160186
161- def get_pod_versions (specs_repo , pods = PODS ):
187+ def get_pod_versions (specs_repo , pods = PODS , ignore_pods = None ,
188+ allow_experimental = False ):
162189 """Get available pods and their versions from the specs repo
163190
164191 Args:
165192 local_repo_dir (str): Directory mirroring Cocoapods specs repo
166193 pods (iterable(str), optional): List of pods whose versions we need.
167194 Defaults to PODS.
168-
195+ ignore_pods (list[str], optional): Case insensitive list of substrings
196+ If any of these substrings are present in the pod name, it will not be
197+ updated.
198+ Eg: ['foo', 'bar'] will ignore all pods that have 'foo' or
199+ 'bar' in their name. For example, 'test_foo', 'test_foo_baz'
200+ allow_experimental (bool): Allow experimental versions.
201+ Eg: 1.2.3-alpha, 1.2.3-beta, 1.2.3-rc
169202 Returns:
170203 dict: Map of the form {<str>:list(str)}
171204 Containing a mapping of podnames to available versions.
172205 """
206+ if ignore_pods is None :
207+ ignore_pods = []
208+
173209 all_versions = defaultdict (list )
174210 logging .info ('Fetching pod versions from Specs repo...' )
175211 podspec_files = get_files_from_directory (specs_repo ,
176212 file_extension = '.podspec.json' )
177213 for podspec_file in podspec_files :
178214 filename = os .path .basename (podspec_file )
179- # Example: FirebaseAuth.podspec.json
215+ # Example: FirebaseAuth.podspec.json --> FirebaseAuth
180216 podname = filename .split ('.' )[0 ]
181- if not podname in pods :
217+ if podname not in pods :
218+ continue
219+ if any (ignore_pod .lower () in podname .lower () for ignore_pod in ignore_pods ):
182220 continue
183221 parent_dir = os .path .dirname (podspec_file )
184222 version = os .path .basename (parent_dir )
223+ if not allow_experimental and '-cppsdk' not in version :
224+ if not re .match (RE_NON_EXPERIMENTAL_VERSION , version ):
225+ continue
185226 all_versions [podname ].append (version )
186227
187228 return all_versions
188229
189230
190- def get_latest_pod_versions (specs_repo = None , pods = PODS ):
231+ def get_latest_pod_versions (specs_repo = None , pods = PODS , ignore_pods = None ,
232+ allow_experimental = None ):
191233 """Get latest versions for specified pods.
192234
193235 Args:
194236 pods (iterable(str) optional): Pods for which we need latest version.
195237 Defaults to PODS.
196238 specs_repo (str optional): Local checkout of Cocoapods specs repo.
239+ ignore_pods (list[str], optional): Case insensitive list of substrings
240+ If any of these substrings are present in the pod name, it will not be
241+ updated.
242+ Eg: ['Foo', 'bar'] will ignore all pods that have 'foo' or
243+ 'bar' in their name. For example, 'test_foo', 'test_foo_baz'
244+ allow_experimental (bool): Allow experimental versions.
245+ Eg: 1.2.3-alpha, 1.2.3-beta, 1.2.3-rc
197246
198247 Returns:
199248 dict: Map of the form {<str>:<str>} containing a mapping of podnames to
200249 latest version.
201250 """
251+ if ignore_pods is None :
252+ ignore_pods = []
253+
202254 cleanup_required = False
203255 if specs_repo is None :
204256 specs_repo = tempfile .mkdtemp (suffix = 'pods' )
@@ -209,7 +261,8 @@ def get_latest_pod_versions(specs_repo=None, pods=PODS):
209261 # Temporary directory should be cleaned up after use.
210262 cleanup_required = True
211263
212- all_versions = get_pod_versions (specs_repo , pods )
264+ all_versions = get_pod_versions (specs_repo , pods , ignore_pods ,
265+ allow_experimental )
213266 if cleanup_required :
214267 shutil .rmtree (specs_repo )
215268
@@ -304,14 +357,16 @@ def modify_pod_file(pod_file, pod_version_map, dryrun=True):
304357 print ()
305358
306359
360+ # Regex to match lines like:
361+ # | | Firebase/Auth Cocoapod (8.2.0)
307362RE_README_POD_VERSION = re .compile (
308363 r"\|(?P<spaces>\s+)\| (?P<pod_name>.*) Cocoapod \((?P<version>([0-9.]+))\)" )
309364
310365def modify_readme_file_pods (readme_filepath , version_map , dryrun = True ):
311366 """Modify a readme Markdown file to reference correct cocoapods versions.
312367
313368 Looks for lines like:
314- <br>Firebase/AdMob Cocoapod (7.11.0)<br> Firebase/Auth Cocoapod (8.1 .0)
369+ | | Firebase/Auth Cocoapod (8.2 .0)
315370 for pods matching the ones in the version map, and modifies them in-place.
316371
317372 Args:
@@ -373,7 +428,26 @@ def replace_pod_line(m):
373428GMAVEN_GROUP_INDEX = "https://dl.google.com/dl/android/maven2/{0}/group-index.xml"
374429
375430
376- def get_latest_maven_versions ():
431+ def get_latest_maven_versions (ignore_packages = None , allow_experimental = False ):
432+ """Gets latest versions of android packages from google Maven repository.
433+
434+ Args:
435+ ignore_packages (list[str], optional): Case insensitive list of substrings
436+ If any of these substrings are present in the android package name, it
437+ will not be updated.
438+ Eg: ['Foo', 'bar'] will ignore all android packages that have 'foo' or
439+ 'bar' in their name. For example, 'my.android.foo.package',
440+ 'myfoo.android.package'
441+ allow_experimental (bool): Allow experimental versions.
442+ Eg: 1.2.3-alpha, 1.2.3-beta, 1.2.3-rc
443+
444+ Returns:
445+ dict: Dictionary of the form {<str>: list(str)} containing full name of
446+ android package as the key and its list of versions as value.
447+ """
448+ if ignore_packages is None :
449+ ignore_packages = []
450+
377451 latest_versions = {}
378452 response = requests .get (GMAVEN_MASTER_INDEX )
379453 index_xml = ElementTree .fromstring (response .content )
@@ -385,8 +459,16 @@ def get_latest_maven_versions():
385459 for group_child in group_xml :
386460 package_name = group_child .tag .replace ('-' , '_' )
387461 package_full_name = group_name + "." + package_name
388- package_version = group_child .attrib ['versions' ].split (',' )[- 1 ]
389- latest_versions [package_full_name ] = package_version
462+ if any (ignore_package .lower ().replace ('-' , '_' ) in package_full_name .lower ()
463+ for ignore_package in ignore_packages ):
464+ continue
465+ versions = group_child .attrib ['versions' ].split (',' )
466+ if not allow_experimental :
467+ versions = [version for version in versions
468+ if re .match (RE_NON_EXPERIMENTAL_VERSION , version ) or '-cppsdk' in version ]
469+ if versions :
470+ latest_valid_version = versions [- 1 ]
471+ latest_versions [package_full_name ] = latest_valid_version
390472 return latest_versions
391473
392474
@@ -453,15 +535,18 @@ def replace_dependency(m):
453535 print ()
454536
455537
538+ # Regex to match lines like:
539+ # | | com.google.firebase:firebase-auth:1.2.3
540+ # | | com.google.firebase:firebase-auth:1.2.3-alpha
456541RE_README_ANDROID_VERSION = re .compile (
457- r"\|(?P<spaces>\s+)\| (?P<pkg>[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+):([0-9. ]+)" )
542+ r"\|(?P<spaces>\s+)\| (?P<pkg>[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+):([a-zA-Z0-9._- ]+)" )
458543
459544
460545def modify_readme_file_android (readme_filepath , version_map , dryrun = True ):
461546 """Modify a readme Markdown file to reference correct module versions.
462547
463548 Looks for lines like:
464- <br> com.google.firebase:firebase-core:15.0.0<br>
549+ | | com.google.firebase:firebase-auth:1.2.3
465550 for modules matching the ones in the version map, and modifies them in-place.
466551
467552 Args:
@@ -489,7 +574,7 @@ def replace_module_line(m):
489574 pkg = m .group ('pkg' ).replace ('-' , '_' ).replace (':' , '.' )
490575 if pkg not in version_map :
491576 return m .group (0 )
492- repl = '|%s| %s:%s ' % (m .group ('spaces' ), m .group ('pkg' ), version_map [pkg ])
577+ repl = '|%s| %s:%s' % (m .group ('spaces' ), m .group ('pkg' ), version_map [pkg ])
493578 return repl
494579
495580 substituted_pairs = []
@@ -515,8 +600,9 @@ def replace_module_line(m):
515600
516601# Regex to match lines like:
517602# implementation 'com.google.firebase:firebase-auth:1.2.3'
603+ # implementation 'com.google.firebase:firebase-auth:1.2.3-alpha'
518604RE_GRADLE_COMPILE_MODULE = re .compile (
519- r"implementation\s*\'(?P<pkg>[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+):([0-9. ]+)\'" )
605+ r"implementation\s*\'(?P<pkg>[a-zA-Z0-9._-]+:[a-zA-Z0-9._-]+):([a-zA-Z0-9._- ]+)\'" )
520606
521607
522608def modify_gradle_file (gradle_filepath , version_map , dryrun = True ):
@@ -580,16 +666,33 @@ def parse_cmdline_args():
580666 help = 'Just print the replaced lines, DO NOT overwrite any files' )
581667 parser .add_argument ( "--log_level" , default = "info" ,
582668 help = "Logging level (debug, warning, info)" )
669+ # TODO: remove default values when we decide to update androidx for
670+ # gameloop_android as well.
671+ parser .add_argument ('--ignore_directories' , nargs = '+' ,
672+ default = ('gameloop_android' , 'scripts' ),
673+ help = 'Ignore updating any files in these directories (names).' )
674+ parser .add_argument ('--allow_experimental' , action = 'store_true' ,
675+ help = 'Allow updating to experimental versions (eg:1.2.3-alpha))' )
583676 # iOS options
584677 parser .add_argument ('--skip_ios' , action = 'store_true' ,
585- help = 'Skip iOS pod version update.' )
678+ help = 'Skip iOS pod version update completely.' )
679+ # TODO: remove default values when Ads SDK does not need to be pinned.
680+ parser .add_argument ('--ignore_ios_pods' , nargs = '+' ,
681+ default = ('Google-Mobile-Ads-SDK' ,),
682+ help = 'Ignore iOS pods which have any of the items specified in '
683+ 'this list as substrings.' )
586684 parser .add_argument ('--podfiles' , nargs = '+' , default = (os .getcwd (),),
587685 help = 'List of pod files or directories containing podfiles' )
588686 parser .add_argument ('--specs_repo' ,
589687 help = 'Local checkout of github Cocoapods Specs repository' )
590688 # Android options
591689 parser .add_argument ('--skip_android' , action = 'store_true' ,
592- help = 'Skip Android libraries version update.' )
690+ help = 'Skip Android libraries version update completely.' )
691+ # TODO: remove default values when Ads SDK does not need to be pinned.
692+ parser .add_argument ('--ignore_android_packages' , nargs = '+' ,
693+ default = ('firebase-ads' ,),
694+ help = 'Ignore Android packages which have any of the items '
695+ 'specified in this list as substrings.' )
593696 parser .add_argument ('--depfiles' , nargs = '+' ,
594697 default = ('Android/firebase_dependencies.gradle' ,
595698 'release_build_files/Android/firebase_dependencies.gradle' ),
@@ -630,17 +733,21 @@ def main():
630733 file_name = 'readme' )
631734
632735 if not args .skip_ios :
633- latest_pod_versions_map = get_latest_pod_versions (args .specs_repo , PODS )
634- pod_files = get_files (args .podfiles , file_extension = '' , file_name = 'Podfile' )
736+ latest_pod_versions_map = get_latest_pod_versions (args .specs_repo , PODS ,
737+ set (args .ignore_ios_pods ), args .allow_experimental )
738+ pod_files = get_files (args .podfiles , file_extension = '' , file_name = 'Podfile' ,
739+ ignore_directories = set (args .ignore_directories ))
635740 for pod_file in pod_files :
636741 modify_pod_file (pod_file , latest_pod_versions_map , args .dryrun )
637742 for readme_file in readme_files :
638743 modify_readme_file_pods (readme_file , latest_pod_versions_map , args .dryrun )
639744
640745 if not args .skip_android :
641- latest_android_versions_map = get_latest_maven_versions ()
746+ latest_android_versions_map = get_latest_maven_versions (
747+ set (args .ignore_android_packages ), args .allow_experimental )
642748 dep_files = get_files (args .depfiles , file_extension = '.gradle' ,
643- file_name = 'firebase_dependencies.gradle' )
749+ file_name = 'firebase_dependencies.gradle' ,
750+ ignore_directories = set (args .ignore_directories ))
644751 for dep_file in dep_files :
645752 modify_dependency_file (dep_file , latest_android_versions_map , args .dryrun )
646753
@@ -649,7 +756,8 @@ def main():
649756 args .dryrun )
650757
651758 gradle_files = get_files (args .gradlefiles , file_extension = '.gradle' ,
652- file_name = 'build.gradle' )
759+ file_name = 'build.gradle' ,
760+ ignore_directories = set (args .ignore_directories ))
653761 for gradle_file in gradle_files :
654762 modify_gradle_file (gradle_file , latest_android_versions_map , args .dryrun )
655763
0 commit comments