Skip to content

Commit dec28d0

Browse files
authored
Merge pull request #536 from firebase/feature/update-versions-improvements
Feature/update versions improvements
2 parents 463289a + d554ba5 commit dec28d0

File tree

1 file changed

+142
-34
lines changed

1 file changed

+142
-34
lines changed

scripts/update_ios_android_dependencies.py renamed to scripts/update_android_ios_dependencies.py

Lines changed: 142 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,30 @@
3434
Usage:
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+
4961
Other similar flags:
5062
--depfiles
5163
--readmefiles
@@ -66,12 +78,12 @@
6678
import tempfile
6779

6880
from collections import defaultdict
69-
from pkg_resources import packaging
7081
from xml.etree import ElementTree
82+
from pkg_resources import packaging
7183

7284

7385
def 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)
307362
RE_README_POD_VERSION = re.compile(
308363
r"\|(?P<spaces>\s+)\| (?P<pod_name>.*) Cocoapod \((?P<version>([0-9.]+))\)")
309364

310365
def 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):
373428
GMAVEN_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
456541
RE_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

460545
def 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'
518604
RE_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

522608
def 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

Comments
 (0)