|
12 | 12 | # init rich console |
13 | 13 | console = Console() |
14 | 14 |
|
| 15 | +# Track packages currently being installed to prevent circular dependencies |
| 16 | +_installing = set() |
| 17 | + |
15 | 18 | def main(package, noconfirm=False): |
16 | 19 | # first check if the name is correct, autocorrect if not |
17 | 20 | # using umbrella/autocorrect_package.py (unlicense) |
18 | 21 | autocorrected = Autocorrect.main(package) |
19 | 22 | if autocorrected != package: |
20 | 23 | package = autocorrected |
21 | 24 |
|
22 | | - # save time fetching scripts, check if the package is in lists quickly |
23 | | - if get_package_list.main(package) != True: |
24 | | - return False |
25 | | - |
26 | | - # main |
| 25 | + # Prevent circular dependencies and infinite loops |
| 26 | + if package in _installing: |
| 27 | + return |
| 28 | + _installing.add(package) |
27 | 29 | try: |
28 | | - # save installed package |
29 | | - repro_path = "/home/" + os.getlogin() + "/.config/repro.car" |
30 | | - |
31 | | - def read_installed_versions(path): |
32 | | - versions = {} |
33 | | - if not os.path.isfile(path): |
| 30 | + # save time fetching scripts, check if the package is in lists quickly |
| 31 | + if get_package_list.main(package) != True: |
| 32 | + return False |
| 33 | + |
| 34 | + # main |
| 35 | + try: |
| 36 | + # save installed package |
| 37 | + repro_path = "/home/" + os.getlogin() + "/.config/repro.car" |
| 38 | + |
| 39 | + def read_installed_versions(path): |
| 40 | + versions = {} |
| 41 | + if not os.path.isfile(path): |
| 42 | + return versions |
| 43 | + with open(path, "r") as f: |
| 44 | + for line in f.read().splitlines(): |
| 45 | + if not line.strip(): |
| 46 | + continue |
| 47 | + if "=" in line: |
| 48 | + name, ver = line.split("=", 1) |
| 49 | + versions[name.strip()] = ver.strip() |
| 50 | + else: |
| 51 | + versions[line.strip()] = "" |
34 | 52 | return versions |
35 | | - with open(path, "r") as f: |
36 | | - for line in f.read().splitlines(): |
37 | | - if not line.strip(): |
38 | | - continue |
39 | | - if "=" in line: |
40 | | - name, ver = line.split("=", 1) |
41 | | - versions[name.strip()] = ver.strip() |
| 53 | + |
| 54 | + def write_installed_versions(path, versions): |
| 55 | + lines = [] |
| 56 | + for name, ver in versions.items(): |
| 57 | + if ver: |
| 58 | + lines.append(f"{name}={ver}") |
42 | 59 | else: |
43 | | - versions[line.strip()] = "" |
44 | | - return versions |
45 | | - |
46 | | - def write_installed_versions(path, versions): |
47 | | - lines = [] |
48 | | - for name, ver in versions.items(): |
49 | | - if ver: |
50 | | - lines.append(f"{name}={ver}") |
51 | | - else: |
52 | | - lines.append(name) |
53 | | - with open(path, "w") as f: |
54 | | - f.write("\n".join(lines) + "\n") |
| 60 | + lines.append(name) |
| 61 | + with open(path, "w") as f: |
| 62 | + f.write("\n".join(lines) + "\n") |
55 | 63 |
|
56 | | - # go to /tmp |
57 | | - os.chdir("/tmp/") |
| 64 | + # go to /tmp |
| 65 | + os.chdir("/tmp/") |
58 | 66 |
|
59 | | - status("Fetching package") |
| 67 | + status("Fetching package") |
60 | 68 |
|
61 | | - # fetch all repos for the install script |
62 | | - found = False |
| 69 | + # fetch all repos for the install script |
| 70 | + found = False |
63 | 71 |
|
64 | | - for mirror in mirrors.install_script_places: |
65 | | - url = f"{mirror.rstrip('/')}/{package}/install_script" |
| 72 | + for mirror in mirrors.install_script_places: |
| 73 | + url = f"{mirror.rstrip('/')}/{package}/install_script" |
66 | 74 |
|
67 | | - result = os.system(f"curl -s -L -o install_script.py {url}") |
| 75 | + result = os.system(f"curl -s -L -o install_script.py {url}") |
68 | 76 |
|
69 | | - with open("install_script.py", "r") as f: |
70 | | - script = f.read() |
| 77 | + with open("install_script.py", "r") as f: |
| 78 | + script = f.read() |
71 | 79 |
|
72 | | - # github returns 404: Not Found in case the file does not exist, |
73 | | - # so handle that case too |
74 | | - if script != "404: Not Found": |
75 | | - found = True |
76 | | - break |
| 80 | + # github returns 404: Not Found in case the file does not exist, |
| 81 | + # so handle that case too |
| 82 | + if script != "404: Not Found": |
| 83 | + found = True |
| 84 | + break |
| 85 | + else: |
| 86 | + pass |
| 87 | + |
| 88 | + if not found: |
| 89 | + status("Failed to fetch install script from all mirrors", "error") |
| 90 | + exit(1) |
| 91 | + |
| 92 | + # import the script as a python library |
| 93 | + spec = importlib.util.spec_from_file_location("install_script", "/tmp/install_script.py") |
| 94 | + install_script = importlib.util.module_from_spec(spec) |
| 95 | + spec.loader.exec_module(install_script) |
| 96 | + |
| 97 | + # check for version |
| 98 | + script_version = None |
| 99 | + if hasattr(install_script, "VERSION"): |
| 100 | + script_version = getattr(install_script, "VERSION") |
| 101 | + elif hasattr(install_script, "version"): |
| 102 | + script_version = getattr(install_script, "version") |
| 103 | + if script_version is None: |
| 104 | + script_version = "" |
77 | 105 | else: |
78 | | - pass |
79 | | - |
80 | | - if not found: |
81 | | - status("Failed to fetch install script from all mirrors", "error") |
82 | | - exit(1) |
83 | | - |
84 | | - # import the script as a python library |
85 | | - spec = importlib.util.spec_from_file_location("install_script", "/tmp/install_script.py") |
86 | | - install_script = importlib.util.module_from_spec(spec) |
87 | | - spec.loader.exec_module(install_script) |
88 | | - |
89 | | - # check for version |
90 | | - script_version = None |
91 | | - if hasattr(install_script, "VERSION"): |
92 | | - script_version = getattr(install_script, "VERSION") |
93 | | - elif hasattr(install_script, "version"): |
94 | | - script_version = getattr(install_script, "version") |
95 | | - if script_version is None: |
96 | | - script_version = "" |
97 | | - else: |
98 | | - script_version = script_version |
99 | | - |
100 | | - # reinstall if new version available |
101 | | - installed_versions = read_installed_versions(repro_path) |
102 | | - installed_version = installed_versions.get(package) |
103 | | - if installed_version is not None and installed_version == script_version: |
104 | | - status(f"{package} is up to date ({script_version}). Exiting.", "ok") |
105 | | - return |
106 | | - elif installed_version is not None and installed_version != script_version: |
107 | | - status(f"New version available for {package}: {installed_version} -> {script_version}. Reinstalling.", "warn") |
108 | | - elif installed_version is None: |
109 | | - if not os.path.isfile(repro_path): |
110 | | - status("No repro.car file. Creating", "warn") |
111 | | - with open(repro_path, "w") as f: |
112 | | - f.write("") |
113 | | - |
114 | | - # beforeinst hook |
115 | | - if hasattr(install_script, "beforeinst"): |
116 | | - install_script.beforeinst() |
117 | | - |
118 | | - # ask for confirmation |
119 | | - if not noconfirm: |
120 | | - status("The following packages are going to be installed:") |
121 | | - print(" ", end="") |
122 | | - for i in install_script.car_deps: |
123 | | - print(i, end=", ") |
124 | | - print(package) # appending to the list did not work for some reason |
125 | | - console.print("::", style="blue bold", end=" ") |
126 | | - sure = input("Install dependencies and build? (Y/n) ") |
127 | | - if sure not in ("", "y", "Y"): |
128 | | - return |
| 106 | + script_version = script_version |
129 | 107 |
|
130 | | - # install deps |
131 | | - if "deps" in script: |
132 | | - try: |
133 | | - install_script.deps() |
134 | | - except Exception:pass |
135 | | - if "car_deps" in script: |
136 | | - for i in install_script.car_deps: |
137 | | - install.main(i, noconfirm=True) |
138 | | - |
139 | | - # build |
140 | | - if "build" in script: |
141 | | - try: |
142 | | - install_script.build() |
143 | | - except Exception:pass |
144 | | - |
145 | | - # ask for confirmation |
146 | | - # todo: make confirmation prompts better by mixing into one |
147 | | - if not noconfirm: |
148 | | - console.print("::", style="blue bold", end=" ") |
149 | | - sure = input("Install? (Y/n) ") |
150 | | - if sure not in ("", "y", "Y"): |
| 108 | + # reinstall if new version available |
| 109 | + installed_versions = read_installed_versions(repro_path) |
| 110 | + installed_version = installed_versions.get(package) |
| 111 | + if installed_version is not None and installed_version == script_version: |
| 112 | + status(f"{package} is up to date ({script_version}). Exiting.", "ok") |
151 | 113 | return |
152 | | - |
153 | | - # install, required hook |
154 | | - status("Installing", "ok") |
155 | | - install_script.install() |
156 | | - |
157 | | - |
158 | | - if not "DoNotWriteVersion = True" in script: |
159 | | - installed_versions[package] = script_version |
160 | | - write_installed_versions(repro_path, installed_versions) |
161 | | - |
162 | | - hooks.post_inst(package) |
163 | | - |
164 | | - # postinst hook |
165 | | - if hasattr(install_script, "postinst"): |
166 | | - install_script.postinst() |
167 | | - |
168 | | - # clean up |
169 | | - os.system("rm -f /tmp/install_script.py") |
170 | | - except Exception: |
171 | | - # crash, print exception and exit |
172 | | - console.print("::", style="red", end=" ") |
173 | | - console.print("Unhandled exception") |
174 | | - console.print_exception() |
175 | | - sys.exit(1) |
176 | | - except KeyboardInterrupt: |
177 | | - # keyboard interrupt, suggest cleaning up |
178 | | - status("Installation interrupted", "error") |
179 | | - status("There might be some files that were installed, but not removed. You can remove them manually.", "warn") |
180 | | - status("If you want to remove them, run the following command:", "warn") |
181 | | - status("sudo car delete " + package, "warn") |
182 | | - sys.exit(1) |
| 114 | + elif installed_version is not None and installed_version != script_version: |
| 115 | + status(f"New version available for {package}: {installed_version} -> {script_version}. Reinstalling.", "warn") |
| 116 | + elif installed_version is None: |
| 117 | + if not os.path.isfile(repro_path): |
| 118 | + status("No repro.car file. Creating", "warn") |
| 119 | + with open(repro_path, "w") as f: |
| 120 | + f.write("") |
| 121 | + |
| 122 | + # beforeinst hook |
| 123 | + if hasattr(install_script, "beforeinst"): |
| 124 | + install_script.beforeinst() |
| 125 | + |
| 126 | + # ask for confirmation |
| 127 | + if not noconfirm: |
| 128 | + status("The following packages are going to be installed:") |
| 129 | + print(" ", end="") |
| 130 | + for i in install_script.car_deps: |
| 131 | + print(i, end=", ") |
| 132 | + print(package) # appending to the list did not work for some reason |
| 133 | + console.print("::", style="blue bold", end=" ") |
| 134 | + sure = input("Install dependencies and build? (Y/n) ") |
| 135 | + if sure not in ("", "y", "Y"): |
| 136 | + return |
| 137 | + |
| 138 | + # install deps |
| 139 | + if "deps" in script: |
| 140 | + try: |
| 141 | + install_script.deps() |
| 142 | + except Exception:pass |
| 143 | + if "car_deps" in script: |
| 144 | + for i in install_script.car_deps: |
| 145 | + install.main(i, noconfirm=True) |
| 146 | + |
| 147 | + # build |
| 148 | + if "build" in script: |
| 149 | + try: |
| 150 | + install_script.build() |
| 151 | + except Exception:pass |
| 152 | + |
| 153 | + # ask for confirmation |
| 154 | + # todo: make confirmation prompts better by mixing into one |
| 155 | + if not noconfirm: |
| 156 | + console.print("::", style="blue bold", end=" ") |
| 157 | + sure = input("Install? (Y/n) ") |
| 158 | + if sure not in ("", "y", "Y"): |
| 159 | + return |
| 160 | + |
| 161 | + # install, required hook |
| 162 | + status("Installing", "ok") |
| 163 | + install_script.install() |
| 164 | + |
| 165 | + |
| 166 | + if not "DoNotWriteVersion = True" in script: |
| 167 | + installed_versions[package] = script_version |
| 168 | + write_installed_versions(repro_path, installed_versions) |
| 169 | + |
| 170 | + hooks.post_inst(package) |
| 171 | + |
| 172 | + # postinst hook |
| 173 | + if hasattr(install_script, "postinst"): |
| 174 | + install_script.postinst() |
| 175 | + |
| 176 | + # clean up |
| 177 | + os.system("sudo rm -f /tmp/install_script.py") |
| 178 | + except Exception: |
| 179 | + # crash, print exception and exit |
| 180 | + console.print("::", style="red", end=" ") |
| 181 | + console.print("Unhandled exception") |
| 182 | + console.print_exception() |
| 183 | + sys.exit(1) |
| 184 | + except KeyboardInterrupt: |
| 185 | + # keyboard interrupt, suggest cleaning up |
| 186 | + status("Installation interrupted", "error") |
| 187 | + status("There might be some files that were installed, but not removed. You can remove them manually.", "warn") |
| 188 | + status("If you want to remove them, run the following command:", "warn") |
| 189 | + status("sudo car delete " + package, "warn") |
| 190 | + sys.exit(1) |
| 191 | + finally: |
| 192 | + # Always remove from installing set, even if installation fails |
| 193 | + _installing.discard(package) |
0 commit comments