forked from vulcanize/vulcanizedb
-
Notifications
You must be signed in to change notification settings - Fork 8
/
gomoderator.py
138 lines (121 loc) · 5.55 KB
/
gomoderator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import os
import sys
import subprocess
import errno
from typing import List, Dict
"""
Resolves dependency conflicts between a plugin repository's and the core repository's go.mods
Usage: python3 gomoderator.py {path_to_core_repository} {path_to_plugin_repository}
"""
ERROR_INVALID_NAME = 123
def is_pathname_valid(pathname: str) -> bool:
"""
`True` if the passed pathname is a valid pathname for the current OS;
`False` otherwise.
"""
try:
if not isinstance(pathname, str) or not pathname:
return False
_, pathname = os.path.splitdrive(pathname)
root_dirname = os.environ.get('HOMEDRIVE', 'C:') \
if sys.platform == 'win32' else os.path.sep
assert os.path.isdir(root_dirname) # ...Murphy and her ironclad Law
root_dirname = root_dirname.rstrip(os.path.sep) + os.path.sep
for pathname_part in pathname.split(os.path.sep):
try:
os.lstat(root_dirname + pathname_part)
except OSError as exc:
if hasattr(exc, 'winerror'):
if exc.winerror == ERROR_INVALID_NAME:
return False
elif exc.errno in {errno.ENAMETOOLONG, errno.ERANGE}:
return False
except TypeError as exc:
return False
else:
return True
def map_deps_to_version(deps_arr: List[str]) -> Dict[str, str]:
mapping = {}
for d in deps_arr:
if d.find(' => ') != -1:
ds = d.split(' => ')
d = ds[1]
d = d.replace(" v", "[>v") # might be able to just split on the empty space not _v and skip this :: insertion
d_and_v = d.split("[>")
mapping[d_and_v[0]] = d_and_v[1]
return mapping
# argument checks
assert len(sys.argv) == 3, "need core repository and plugin repository path arguments"
core_repository_path = sys.argv[1]
plugin_repository_path = sys.argv[2]
assert is_pathname_valid(core_repository_path), "core repository path argument is not valid"
assert is_pathname_valid(plugin_repository_path), "plugin repository path argument is not valid"
# collect `go list -m all` output from both repositories; remain in the plugin repository
os.chdir(core_repository_path)
core_deps_b = subprocess.check_output(["go", "list", "-m", "all"])
os.chdir(plugin_repository_path)
plugin_deps_b = subprocess.check_output(["go", "list", "-m", "all"])
core_deps = core_deps_b.decode("utf-8")
core_deps_arr = core_deps.splitlines()
del core_deps_arr[0] # first line is the project repo itself
plugin_deps = plugin_deps_b.decode("utf-8")
plugin_deps_arr = plugin_deps.splitlines()
del plugin_deps_arr[0]
core_deps_mapping = map_deps_to_version(core_deps_arr)
plugin_deps_mapping = map_deps_to_version(plugin_deps_arr)
# iterate over dependency maps for both repos and find version conflicts
# attempt to resolve conflicts by adding adding a `require` for the core version to the plugin's go.mod file
none = True
for dep, core_version in core_deps_mapping.items():
if dep in plugin_deps_mapping.keys():
plugin_version = plugin_deps_mapping[dep]
if core_version != plugin_version:
print(f'{dep} has a conflict: core is using version {core_version} '
f'but the plugin is using version {plugin_version}')
fixed_dep = f'{dep}@{core_version}'
print(f'attempting fix by `go mod edit -require={fixed_dep}')
subprocess.check_call(["go", "mod", "edit", f'-require={fixed_dep}'])
none = False
if none:
print("no conflicts to resolve")
sys.exit(0)
# the above process does not work for all dep conflicts e.g. golang.org/x/text v0.3.0 will not stick this way
# so we will try the `go get {dep}` route for any remaining conflicts
updated_plugin_deps_b = subprocess.check_output(["go", "list", "-m", "all"])
updated_plugin_deps = updated_plugin_deps_b.decode("utf-8")
updated_plugin_deps_arr = updated_plugin_deps.splitlines()
del updated_plugin_deps_arr[0]
updated_plugin_deps_mapping = map_deps_to_version(updated_plugin_deps_arr)
none = True
for dep, core_version in core_deps_mapping.items():
if dep in updated_plugin_deps_mapping.keys():
updated_plugin_version = updated_plugin_deps_mapping[dep]
if core_version != updated_plugin_version:
print(f'{dep} still has a conflict: core is using version {core_version} '
f'but the plugin is using version {updated_plugin_version}')
fixed_dep = f'{dep}@{core_version}'
print(f'attempting fix by `go get {fixed_dep}')
subprocess.check_call(["go", "get", fixed_dep])
none = False
if none:
print("all conflicts have been resolved")
sys.exit(0)
# iterate over plugins `go list -m all` output one more time and inform whether or not the above has worked
final_plugin_deps_b = subprocess.check_output(["go", "list", "-m", "all"])
final_plugin_deps = final_plugin_deps_b.decode("utf-8")
final_plugin_deps_arr = final_plugin_deps.splitlines()
del final_plugin_deps_arr[0]
final_plugin_deps_mapping = map_deps_to_version(final_plugin_deps_arr)
none = True
for dep, core_version in core_deps_mapping.items():
if dep in final_plugin_deps_mapping.keys():
final_plugin_version = final_plugin_deps_mapping[dep]
if core_version != final_plugin_version:
print(f'{dep} STILL has a conflict: core is using version {core_version} '
f'but the plugin is using version {final_plugin_version}')
none = False
if none:
print("all conflicts have been resolved")
sys.exit(0)
print("failed to resolve all conflicts")
sys.exit(1)