OMCompiler/Compiler/Script/PackageManagement.mo
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of OpenModelica. | ||
| 3 | * | ||
| 4 | * Copyright (c) 1998-2026, Open Source Modelica Consortium (OSMC), | ||
| 5 | * c/o Linköpings universitet, Department of Computer and Information Science, | ||
| 6 | * SE-58183 Linköping, Sweden. | ||
| 7 | * | ||
| 8 | * All rights reserved. | ||
| 9 | * | ||
| 10 | * THIS PROGRAM IS PROVIDED UNDER THE TERMS OF AGPL VERSION 3 LICENSE OR | ||
| 11 | * THIS OSMC PUBLIC LICENSE (OSMC-PL) VERSION 1.8. | ||
| 12 | * ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS PROGRAM CONSTITUTES | ||
| 13 | * RECIPIENT'S ACCEPTANCE OF THE OSMC PUBLIC LICENSE OR THE GNU AGPL | ||
| 14 | * VERSION 3, ACCORDING TO RECIPIENTS CHOICE. | ||
| 15 | * | ||
| 16 | * The OpenModelica software and the OSMC (Open Source Modelica Consortium) | ||
| 17 | * Public License (OSMC-PL) are obtained from OSMC, either from the above | ||
| 18 | * address, from the URLs: | ||
| 19 | * http://www.openmodelica.org or | ||
| 20 | * https://github.com/OpenModelica/ or | ||
| 21 | * http://www.ida.liu.se/projects/OpenModelica, | ||
| 22 | * and in the OpenModelica distribution. | ||
| 23 | * | ||
| 24 | * GNU AGPL version 3 is obtained from: | ||
| 25 | * https://www.gnu.org/licenses/licenses.html#GPL | ||
| 26 | * | ||
| 27 | * This program is distributed WITHOUT ANY WARRANTY; without | ||
| 28 | * even the implied warranty of MERCHANTABILITY or FITNESS | ||
| 29 | * FOR A PARTICULAR PURPOSE, EXCEPT AS EXPRESSLY SET FORTH | ||
| 30 | * IN THE BY RECIPIENT SELECTED SUBSIDIARY LICENSE CONDITIONS OF OSMC-PL. | ||
| 31 | * | ||
| 32 | * See the full OSMC Public License conditions for more details. | ||
| 33 | * | ||
| 34 | */ | ||
| 35 | |||
| 36 | encapsulated package PackageManagement | ||
| 37 | |||
| 38 | import BaseAvlTree; | ||
| 39 | import JSON; | ||
| 40 | import SemanticVersion; | ||
| 41 | |||
| 42 | protected | ||
| 43 | |||
| 44 | import Autoconf; | ||
| 45 | import AvlSetString; | ||
| 46 | import Curl; | ||
| 47 | import Error; | ||
| 48 | import Global; | ||
| 49 | import List; | ||
| 50 | import Settings; | ||
| 51 | import StringUtil; | ||
| 52 | import System; | ||
| 53 | import Testsuite; | ||
| 54 | import Util; | ||
| 55 | import Unzip; | ||
| 56 | |||
| 57 | public | ||
| 58 | |||
| 59 | encapsulated package AvailableLibraries | ||
| 60 | import BaseAvlTree; | ||
| 61 | import PackageManagement.VersionMap; | ||
| 62 | extends BaseAvlTree; | ||
| 63 | redeclare type Key = String; | ||
| 64 | redeclare type Value = PackageManagement.VersionMap.Tree; | ||
| 65 | redeclare function extends keyStr | ||
| 66 | algorithm | ||
| 67 | outString := inKey; | ||
| 68 | end keyStr; | ||
| 69 | redeclare function extends valueStr | ||
| 70 | algorithm | ||
| 71 | ✗ | outString := PackageManagement.VersionMap.printTreeStr(inValue); | |
| 72 | end valueStr; | ||
| 73 | redeclare function extends keyCompare | ||
| 74 | algorithm | ||
| 75 | ✗ | outResult := stringCompare(inKey1, inKey2); | |
| 76 | end keyCompare; | ||
| 77 | end AvailableLibraries; | ||
| 78 | |||
| 79 | encapsulated package VersionMap | ||
| 80 | import BaseAvlTree; | ||
| 81 | import SemanticVersion; | ||
| 82 | extends BaseAvlTree; | ||
| 83 | redeclare type Key = SemanticVersion.Version; | ||
| 84 | redeclare type Value = String; | ||
| 85 | redeclare function extends keyStr | ||
| 86 | algorithm | ||
| 87 | ✗ | outString := SemanticVersion.toString(inKey); | |
| 88 | end keyStr; | ||
| 89 | redeclare function extends valueStr | ||
| 90 | algorithm | ||
| 91 | outString := inValue; | ||
| 92 | end valueStr; | ||
| 93 | redeclare function extends keyCompare | ||
| 94 | algorithm | ||
| 95 | ✗ | outResult := SemanticVersion.compare(inKey1, inKey2, compareBuildInformation=true); | |
| 96 | end keyCompare; | ||
| 97 | end VersionMap; | ||
| 98 | |||
| 99 | constant String metaDataFileName = "openmodelica.metadata.json"; | ||
| 100 | |||
| 101 | function getInstalledLibraries | ||
| 102 | output AvailableLibraries.Tree tree; | ||
| 103 | protected | ||
| 104 | String mp, gd, first, ver, lib; | ||
| 105 | list<String> mps, files, dirs, rest; | ||
| 106 | VersionMap.Tree versions; | ||
| 107 | algorithm | ||
| 108 | ✗ | mp := Settings.getModelicaPath(Testsuite.isRunning()); | |
| 109 | gd := Autoconf.groupDelimiter; | ||
| 110 | ✗ | mps := System.strtok(mp, gd); | |
| 111 | ✗ | tree := AvailableLibraries.new(); | |
| 112 | files := {}; | ||
| 113 | dirs := {}; | ||
| 114 | ✗ | for mp in mps loop | |
| 115 | ✗ | files := listAppend(list(mp + "/" + file for file in System.moFiles(mp)), files); | |
| 116 | ✗ | dirs := listAppend(list(mp + "/" + dir for dir in getLibrarySubdirectories(mp)), dirs); | |
| 117 | end for; | ||
| 118 | ✗ | for path in listAppend(files, dirs) loop | |
| 119 | ✗ | lib := System.basename(path); | |
| 120 | ✗ | if StringUtil.endsWith(lib, ".mo") then | |
| 121 | ✗ | lib := Util.removeLast3Char(lib); | |
| 122 | end if; | ||
| 123 | ✗ | first::rest := System.strtok(lib, " "); | |
| 124 | ✗ | ver := stringDelimitList(rest, " "); | |
| 125 | ✗ | versions := if AvailableLibraries.hasKey(tree, first) then AvailableLibraries.get(tree, first) else VersionMap.new(); | |
| 126 | ✗ | versions := VersionMap.add(versions, SemanticVersion.parse(ver), path, conflictFunc=VersionMap.addConflictReplace); | |
| 127 | ✗ | tree := AvailableLibraries.add(tree, first, versions, conflictFunc=AvailableLibraries.addConflictReplace); | |
| 128 | end for; | ||
| 129 | end getInstalledLibraries; | ||
| 130 | |||
| 131 | function getInstalledLibraryVersions | ||
| 132 | input String libraryName; | ||
| 133 | output list<String> libraryVersions = {}; | ||
| 134 | protected | ||
| 135 | AvailableLibraries.Tree tree; | ||
| 136 | VersionMap.Tree versionTree; | ||
| 137 | list<SemanticVersion.Version> versions = {}; | ||
| 138 | String versionStr; | ||
| 139 | algorithm | ||
| 140 | ✗ | tree := getInstalledLibraries(); | |
| 141 | ✗ | versionTree := AvailableLibraries.get(tree, libraryName); | |
| 142 | ✗ | versions := VersionMap.listKeys(versionTree); | |
| 143 | ✗ | for version in versions loop | |
| 144 | ✗ | versionStr := VersionMap.keyStr(version); | |
| 145 | ✗ | if (stringCompare(versionStr, "") > 0) then | |
| 146 | libraryVersions := versionStr::libraryVersions; | ||
| 147 | end if; | ||
| 148 | end for; | ||
| 149 | end getInstalledLibraryVersions; | ||
| 150 | |||
| 151 | function getLibrarySubdirectories "This function returns a list of subdirectories that contain a package.mo file." | ||
| 152 | input String inPath; | ||
| 153 | output list<String> outSubdirectories = {}; | ||
| 154 | protected | ||
| 155 | list<String> allSubdirectories = System.subDirectories(inPath); | ||
| 156 | String pd = Autoconf.pathDelimiter; | ||
| 157 | algorithm | ||
| 158 | ✗ | for dir in allSubdirectories loop | |
| 159 | ✗ | if System.regularFileExists(inPath + pd + dir + pd + "package.mo") then | |
| 160 | outSubdirectories := dir::outSubdirectories; | ||
| 161 | end if; | ||
| 162 | end for; | ||
| 163 | end getLibrarySubdirectories; | ||
| 164 | |||
| 165 | function providesExpectedVersion | ||
| 166 | input String version; | ||
| 167 | input JSON provides; | ||
| 168 | input SemanticVersion.Version wantedVersion; | ||
| 169 | output Boolean matches; | ||
| 170 | protected | ||
| 171 | list<String> providedVersions; | ||
| 172 | String str; | ||
| 173 | SemanticVersion.Version thisVersion; | ||
| 174 | algorithm | ||
| 175 | () := match wantedVersion | ||
| 176 | case SemanticVersion.NONSEMVER(str) guard str == "default" or str == "" | ||
| 177 | algorithm | ||
| 178 | matches := true; /* Any version matches the empty */ | ||
| 179 | ✗ | return; | |
| 180 | then fail(); | ||
| 181 | case SemanticVersion.SEMVER(major=0, minor=0, patch=0, prerelease={"default"}) | ||
| 182 | algorithm | ||
| 183 | matches := true; /* Any version matches the empty */ | ||
| 184 | ✗ | return; | |
| 185 | then fail(); | ||
| 186 | else (); | ||
| 187 | end match; | ||
| 188 | ✗ | providedVersions := JSON.getStringList(provides); | |
| 189 | matches := false; | ||
| 190 | ✗ | for v in version::providedVersions loop | |
| 191 | ✗ | thisVersion := SemanticVersion.parse(v, nonsemverAsZeroZeroZero=true); | |
| 192 | ✗ | if SemanticVersion.compare(thisVersion,wantedVersion,comparePrerelease=SemanticVersion.isPrerelease(wantedVersion) and SemanticVersion.isPrerelease(wantedVersion)) == 0 then | |
| 193 | matches := true; | ||
| 194 | ✗ | return; | |
| 195 | end if; | ||
| 196 | end for; | ||
| 197 | end providesExpectedVersion; | ||
| 198 | |||
| 199 | constant list<String> supportLevels = {"fullSupport", "support", "experimental", "obsolete", "unknown", "noSupport"}; | ||
| 200 | type SupportLevel = enumeration(noSupport, unknown, obsolete, experimental, support, fullSupport); | ||
| 201 | |||
| 202 | function getSupportLevel | ||
| 203 | input JSON obj; | ||
| 204 | output SupportLevel support; | ||
| 205 | algorithm | ||
| 206 | support := match obj | ||
| 207 | case JSON.STRING("fullSupport") then SupportLevel.fullSupport; | ||
| 208 | case JSON.STRING("support") then SupportLevel.support; | ||
| 209 | case JSON.STRING("experimental") then SupportLevel.experimental; | ||
| 210 | case JSON.STRING("obsolete") then SupportLevel.obsolete; | ||
| 211 | case JSON.STRING("unknown") then SupportLevel.unknown; | ||
| 212 | case JSON.STRING("noSupport") then SupportLevel.noSupport; | ||
| 213 | else | ||
| 214 | algorithm | ||
| 215 | ✗ | Error.addInternalError("Unknown support level " + JSON.toString(obj), sourceInfo()); | |
| 216 | ✗ | then fail(); | |
| 217 | end match; | ||
| 218 | end getSupportLevel; | ||
| 219 | |||
| 220 | function compareVersionsAndSupportLevel | ||
| 221 | input tuple<String,SemanticVersion.Version,SupportLevel> x1, x2; | ||
| 222 | output Boolean c; | ||
| 223 | protected | ||
| 224 | SupportLevel s1, s2; | ||
| 225 | SemanticVersion.Version v1, v2; | ||
| 226 | algorithm | ||
| 227 | ✗ | (_, v1, s1) := x1; | |
| 228 | ✗ | (_, v2, s2) := x2; | |
| 229 | ✗ | if s1 < s2 then | |
| 230 | c := true; | ||
| 231 | ✗ | return; | |
| 232 | elseif s1 > s2 then | ||
| 233 | c := false; | ||
| 234 | ✗ | return; | |
| 235 | end if; | ||
| 236 | ✗ | if SemanticVersion.isPrerelease(v1) <> SemanticVersion.isPrerelease(v2) then | |
| 237 | ✗ | c := SemanticVersion.isPrerelease(v2); | |
| 238 | ✗ | return; | |
| 239 | end if; | ||
| 240 | ✗ | c := SemanticVersion.compare(v1, v2, compareBuildInformation=true) < 0; | |
| 241 | end compareVersionsAndSupportLevel; | ||
| 242 | |||
| 243 | function updateIndex | ||
| 244 | output Boolean success; | ||
| 245 | protected | ||
| 246 | String userLibraries, packageIndex; | ||
| 247 | constant String url = "https://libraries.openmodelica.org/index/v1/index.json"; | ||
| 248 | algorithm | ||
| 249 | ✗ | userLibraries := getUserLibraryPath(); | |
| 250 | ✗ | Util.createDirectoryTree(userLibraries); | |
| 251 | ✗ | packageIndex := userLibraries + "index.json"; | |
| 252 | ✗ | if not Curl.multiDownload({({url},packageIndex)}) then | |
| 253 | ✗ | Error.addMessage(Error.ERROR_PKG_INDEX_FAILED_DOWNLOAD, {url,packageIndex}); | |
| 254 | success := false; | ||
| 255 | else | ||
| 256 | ✗ | Error.addSourceMessage(Error.NOTIFY_PKG_INDEX_DOWNLOAD, {url}, makeSourceInfo(getIndexPath())); | |
| 257 | success := true; | ||
| 258 | end if; | ||
| 259 | ✗ | setGlobalRoot(Global.packageIndexCacheIndex, 0); | |
| 260 | end updateIndex; | ||
| 261 | |||
| 262 | function upgradeInstalledPackages | ||
| 263 | input Boolean installNewestVersions; | ||
| 264 | input Integer wasmABI = 0; | ||
| 265 | output Boolean success; | ||
| 266 | protected | ||
| 267 | AvailableLibraries.Tree installedLibraries; | ||
| 268 | VersionMap.Tree versions; | ||
| 269 | algorithm | ||
| 270 | success := true; | ||
| 271 | ✗ | installedLibraries := getInstalledLibraries(); | |
| 272 | ✗ | for pkg in AvailableLibraries.listKeys(installedLibraries) loop | |
| 273 | ✗ | versions := AvailableLibraries.get(installedLibraries, pkg); | |
| 274 | ✗ | for version in VersionMap.listKeys(versions) loop | |
| 275 | ✗ | success := success and installPackage(pkg, SemanticVersion.toString(version), exactMatch=true, wasmABI=wasmABI); | |
| 276 | end for; | ||
| 277 | ✗ | if installNewestVersions then | |
| 278 | ✗ | success := success and installPackage(pkg, "", exactMatch=false, wasmABI=wasmABI); | |
| 279 | end if; | ||
| 280 | end for; | ||
| 281 | end upgradeInstalledPackages; | ||
| 282 | |||
| 283 | function getPackageIndex | ||
| 284 | input Boolean printError; | ||
| 285 | output JSON obj; | ||
| 286 | protected | ||
| 287 | String userLibraries, packageIndex, gd, mp; | ||
| 288 | list<String> mps; | ||
| 289 | algorithm | ||
| 290 | try | ||
| 291 | 2251 | obj := getGlobalRoot(Global.packageIndexCacheIndex); | |
| 292 | ✗ | return; | |
| 293 | else | ||
| 294 | end try; | ||
| 295 | 2251 | mp := Settings.getModelicaPath(Testsuite.isRunning()); | |
| 296 | gd := Autoconf.groupDelimiter; | ||
| 297 | 2251 | mps := System.strtok(mp, gd); | |
| 298 | 2251 | userLibraries := getUserLibraryPath(); | |
| 299 | 2251 | packageIndex := userLibraries + "index.json"; | |
| 300 | 2251 | obj := JSON.emptyObject(); | |
| 301 | // User library path ends with forward slash so compare the path with and without leading forward slash | ||
| 302 |
2/4✓ Branch 1 taken 2251 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 2251 times.
✗ Branch 6 not taken.
|
2251 | if not listMember(userLibraries, mps) and not listMember(Util.removeLastNChar(userLibraries, 1), mps) then |
| 303 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2251 times.
|
2251 | if printError then |
| 304 | ✗ | Error.addMessage(Error.ERROR_PKG_INDEX_NOT_ON_PATH, {mp, userLibraries}); | |
| 305 | end if; | ||
| 306 | 2251 | return; | |
| 307 | end if; | ||
| 308 | |||
| 309 | ✗ | if not System.regularFileExists(packageIndex) then | |
| 310 | ✗ | if not updateIndex() then | |
| 311 | ✗ | return; | |
| 312 | end if; | ||
| 313 | end if; | ||
| 314 | try | ||
| 315 | ✗ | obj := JSON.parseFile(packageIndex); | |
| 316 | ✗ | setGlobalRoot(Global.packageIndexCacheIndex, obj); | |
| 317 | else | ||
| 318 | ✗ | Error.addSourceMessage(Error.ERROR_PKG_INDEX_NOT_PARSED, {packageIndex}, makeSourceInfo(getIndexPath())); | |
| 319 | end try; | ||
| 320 | end getPackageIndex; | ||
| 321 | |||
| 322 | function getAllProvidedVersionsForLibrary | ||
| 323 | input String lib; | ||
| 324 | input Boolean printError; | ||
| 325 | output list<String> result; | ||
| 326 | protected | ||
| 327 | JSON obj, libobject, vers, provides; | ||
| 328 | AvlSetString.Tree tree; | ||
| 329 | algorithm | ||
| 330 | ✗ | result := {}; | |
| 331 | ✗ | tree := AvlSetString.new(); | |
| 332 | try | ||
| 333 | ✗ | obj := getPackageIndex(printError); | |
| 334 | ✗ | libobject := JSON.get(JSON.get(obj, "libs"), lib); | |
| 335 | ✗ | vers := JSON.get(libobject, "versions"); | |
| 336 | |||
| 337 | ✗ | for version in JSON.getKeys(vers) loop | |
| 338 | ✗ | tree := AvlSetString.add(tree, version); | |
| 339 | ✗ | provides := JSON.getOrDefault(JSON.get(vers, version), "provides", JSON.emptyArray()); | |
| 340 | |||
| 341 | ✗ | for i in 1:JSON.size(provides) loop | |
| 342 | ✗ | tree := AvlSetString.add(tree, JSON.getString(JSON.at(provides, i))); | |
| 343 | end for; | ||
| 344 | end for; | ||
| 345 | |||
| 346 | ✗ | result := AvlSetString.listKeys(tree); | |
| 347 | else | ||
| 348 | ✗ | return; | |
| 349 | end try; | ||
| 350 | end getAllProvidedVersionsForLibrary; | ||
| 351 | |||
| 352 | function versionsThatProvideTheWanted | ||
| 353 | input String id; | ||
| 354 | input String version; | ||
| 355 | input Boolean printError; | ||
| 356 | output list<String> result; | ||
| 357 | protected | ||
| 358 | JSON obj, libobject, vers; | ||
| 359 | SemanticVersion.Version wantedVersion; | ||
| 360 | algorithm | ||
| 361 | result := {}; | ||
| 362 | try | ||
| 363 | 2251 | obj := getPackageIndex(printError); | |
| 364 | 2251 | libobject := JSON.get(JSON.get(obj, "libs"), id); | |
| 365 | ✗ | vers := JSON.get(libobject, "versions"); | |
| 366 | ✗ | wantedVersion := SemanticVersion.parse(version, nonsemverAsZeroZeroZero=true); | |
| 367 | ✗ | result := List.map(List.sort(list((version,SemanticVersion.parse(version, nonsemverAsZeroZeroZero=true),getSupportLevel(JSON.get(JSON.get(vers, version),"support"))) for version guard providesExpectedVersion(version, JSON.getOrDefault(JSON.get(vers, version), "provides", JSON.emptyArray()), wantedVersion) in JSON.getKeys(vers)), compareVersionsAndSupportLevel), Util.tuple31); | |
| 368 | else | ||
| 369 | 2251 | return; | |
| 370 | end try; | ||
| 371 | end versionsThatProvideTheWanted; | ||
| 372 | |||
| 373 | function versionsThatConvertFromTheWanted | ||
| 374 | "Returns a list of versions that provide conversion from the given version of a library." | ||
| 375 | input String id; | ||
| 376 | input String version; | ||
| 377 | input Boolean printError; | ||
| 378 | output list<String> result; | ||
| 379 | protected | ||
| 380 | JSON obj, libobject, vers; | ||
| 381 | SemanticVersion.Version wantedVersion, convertVersion; | ||
| 382 | JSON convertFrom; | ||
| 383 | String versionStr; | ||
| 384 | algorithm | ||
| 385 | result := {}; | ||
| 386 | try | ||
| 387 | ✗ | obj := getPackageIndex(printError); | |
| 388 | ✗ | libobject := JSON.get(JSON.get(obj, "libs"), id); | |
| 389 | ✗ | vers := JSON.get(libobject, "versions"); | |
| 390 | ✗ | wantedVersion := SemanticVersion.parse(version, nonsemverAsZeroZeroZero=true); | |
| 391 | |||
| 392 | ✗ | for v in JSON.getKeys(vers) loop | |
| 393 | ✗ | convertFrom := JSON.getOrDefault(JSON.get(vers, v), "convertFromVersion", JSON.emptyArray()); | |
| 394 | |||
| 395 | ✗ | for i in 1:JSON.size(convertFrom) loop | |
| 396 | ✗ | JSON.STRING(versionStr) := JSON.at(convertFrom, i); | |
| 397 | ✗ | convertVersion := SemanticVersion.parse(versionStr, nonsemverAsZeroZeroZero=true); | |
| 398 | |||
| 399 | ✗ | if SemanticVersion.compare(wantedVersion, convertVersion) == 0 then | |
| 400 | result := v :: result; | ||
| 401 | ✗ | continue; | |
| 402 | end if; | ||
| 403 | end for; | ||
| 404 | end for; | ||
| 405 | else | ||
| 406 | ✗ | return; | |
| 407 | end try; | ||
| 408 | end versionsThatConvertFromTheWanted; | ||
| 409 | |||
| 410 | function versionsThatConvertToTheWanted | ||
| 411 | "Returns a list of versions that can be converted to the given version of a library." | ||
| 412 | input String id; | ||
| 413 | input String version; | ||
| 414 | input Boolean printError; | ||
| 415 | output list<String> result; | ||
| 416 | protected | ||
| 417 | JSON obj, libobject, vers; | ||
| 418 | SemanticVersion.Version wantedVersion, libVersion; | ||
| 419 | algorithm | ||
| 420 | result := {}; | ||
| 421 | try | ||
| 422 | ✗ | obj := getPackageIndex(printError); | |
| 423 | ✗ | libobject := JSON.get(JSON.get(obj, "libs"), id); | |
| 424 | ✗ | vers := JSON.get(libobject, "versions"); | |
| 425 | ✗ | wantedVersion := SemanticVersion.parse(version, nonsemverAsZeroZeroZero=true); | |
| 426 | |||
| 427 | ✗ | for v in JSON.getKeys(vers) loop | |
| 428 | ✗ | libVersion := SemanticVersion.parse(v, nonsemverAsZeroZeroZero=true); | |
| 429 | |||
| 430 | ✗ | if SemanticVersion.compare(wantedVersion, libVersion) == 0 then | |
| 431 | ✗ | result := JSON.getStringList(JSON.get(JSON.get(vers, v), "convertFromVersion")); | |
| 432 | ✗ | return; | |
| 433 | end if; | ||
| 434 | end for; | ||
| 435 | else | ||
| 436 | ✗ | return; | |
| 437 | end try; | ||
| 438 | end versionsThatConvertToTheWanted; | ||
| 439 | |||
| 440 | function installPackage | ||
| 441 | input String pkg; | ||
| 442 | input String version; | ||
| 443 | input Boolean exactMatch; | ||
| 444 | input Boolean skipDownload = false; | ||
| 445 | input Integer wasmABI = 0 "Also install the prebuilt wasm external \"C\" modules of this ABI, if the index has any"; | ||
| 446 | output Boolean success; | ||
| 447 | output list<String> wasmDirs = {} "The libraries prebuilt wasm modules were unpacked into"; | ||
| 448 | protected | ||
| 449 | list<PackageInstallInfo> packageList, packagesToInstall; | ||
| 450 | list<tuple<list<String>,String>> urlPathList, urlPathListToDownload; | ||
| 451 | String path, destPath, destPathPkgMo, destPathPkgInfo, oldSha, dirOfPath, expectedLocation, cachePath=getCachePath(), installCachePath=getInstallationCachePath(), curCachePath; | ||
| 452 | list<String> mirrors; | ||
| 453 | list<tuple<String,String>> wasmZips; | ||
| 454 | algorithm | ||
| 455 | ✗ | (success,packageList) := installPackageWork(pkg, version, exactMatch, false, {}); | |
| 456 | ✗ | for p in packageList loop | |
| 457 | ✗ | if p.pkg == pkg and not p.needsInstall then | |
| 458 | ✗ | if version == SemanticVersion.toString(p.version) then | |
| 459 | ✗ | Error.addSourceMessage(Error.NOTIFY_PKG_ALREADY_INSTALLED, {pkg, SemanticVersion.toString(p.version)}, makeSourceInfo(p.path)); | |
| 460 | else | ||
| 461 | ✗ | Error.addSourceMessage(Error.NOTIFY_PKG_NO_INSTALL, {pkg, version, SemanticVersion.toString(p.version)}, makeSourceInfo(p.path)); | |
| 462 | end if; | ||
| 463 | end if; | ||
| 464 | end for; | ||
| 465 | ✗ | packagesToInstall := list(p for p guard p.needsInstall in packageList); | |
| 466 | ✗ | if not skipDownload then | |
| 467 | ✗ | for p in packageList loop | |
| 468 | ✗ | if not p.needsInstall then | |
| 469 | ✗ | if installMissingWasm(p.path, wasmExternalsZips(p.json, wasmABI), cachePath) then | |
| 470 | ✗ | wasmDirs := p.path :: wasmDirs; | |
| 471 | end if; | ||
| 472 | end if; | ||
| 473 | end for; | ||
| 474 | end if; | ||
| 475 | |||
| 476 | ✗ | for pack in packagesToInstall loop | |
| 477 | ✗ | Util.createDirectoryTree(cachePath); | |
| 478 | end for; | ||
| 479 | |||
| 480 | ✗ | if not skipDownload then | |
| 481 | ✗ | mirrors := getMirrors(); | |
| 482 | ✗ | urlPathList := List.sort(list((getAllUrls(p.urlToZipFile, mirrors), if System.regularFileExists(installCachePath + System.basename(p.urlToZipFile)) then installCachePath + System.basename(p.urlToZipFile) else cachePath + System.basename(p.urlToZipFile)) for p in packagesToInstall), compareUrlBool); | |
| 483 | ✗ | urlPathList := listAppend(list(({Util.tuple21(z)}, cachePath + System.basename(Util.tuple21(z))) for z in List.flatten(list(wasmExternalsZips(p.json, wasmABI) for p in packagesToInstall))), urlPathList); | |
| 484 | ✗ | urlPathList := List.unique(urlPathList); | |
| 485 | ✗ | urlPathListToDownload := list(tpl for tpl guard not System.regularFileExists(Util.tuple22(tpl)) in urlPathList); | |
| 486 | ✗ | if not Curl.multiDownload(urlPathListToDownload) then | |
| 487 | ✗ | fail(); | |
| 488 | end if; | ||
| 489 | end if; | ||
| 490 | |||
| 491 | ✗ | for pack in packagesToInstall loop | |
| 492 | ✗ | destPath := getUserLibraryPath() + pack.pkg + " " + SemanticVersion.toString(pack.version); | |
| 493 | |||
| 494 | ✗ | System.removeDirectory(destPath); | |
| 495 | ✗ | System.createDirectory(destPath); | |
| 496 | |||
| 497 | ✗ | destPathPkgMo := destPath + "/package.mo"; | |
| 498 | ✗ | destPathPkgInfo := destPath + "/" + metaDataFileName; | |
| 499 | ✗ | oldSha := ""; | |
| 500 | ✗ | if System.regularFileExists(destPathPkgInfo) then | |
| 501 | try | ||
| 502 | ✗ | oldSha := getShaOrZipfile(JSON.parseFile(destPathPkgInfo)); | |
| 503 | else | ||
| 504 | end try; | ||
| 505 | end if; | ||
| 506 | |||
| 507 | ✗ | curCachePath := if System.regularFileExists(installCachePath + System.basename(pack.urlToZipFile)) then installCachePath else cachePath; | |
| 508 | ✗ | if StringUtil.endsWith(pack.path, ".mo") then | |
| 509 | // We are not copying a full directory, so also look for Resources in the zip-file | ||
| 510 | ✗ | dirOfPath := System.dirname(pack.path); | |
| 511 | ✗ | if pack.singleFileStructureCopyAllFiles then | |
| 512 | ✗ | Unzip.unzipPath(curCachePath + System.basename(pack.urlToZipFile), if dirOfPath =="." then "" else dirOfPath, destPath); | |
| 513 | ✗ | expectedLocation := destPath + "/" + System.basename(pack.path); | |
| 514 | ✗ | if not System.rename(expectedLocation, destPathPkgMo) then | |
| 515 | ✗ | Error.addMessage(Error.ERROR_PKG_INSTALL_NO_PACKAGE_MO, {curCachePath + System.basename(pack.urlToZipFile), expectedLocation}); | |
| 516 | // System.removeDirectory(destPath); | ||
| 517 | ✗ | fail(); | |
| 518 | end if; | ||
| 519 | else | ||
| 520 | ✗ | Unzip.unzipPath(curCachePath + System.basename(pack.urlToZipFile), pack.path, destPathPkgMo); | |
| 521 | end if; | ||
| 522 | else | ||
| 523 | ✗ | Unzip.unzipPath(curCachePath + System.basename(pack.urlToZipFile), pack.path, destPath); | |
| 524 | end if; | ||
| 525 | |||
| 526 | ✗ | if System.regularFileExists(destPathPkgMo) then | |
| 527 | ✗ | wasmZips := list(z for z guard System.regularFileExists(cachePath + System.basename(Util.tuple21(z))) in wasmExternalsZips(pack.json, wasmABI)); | |
| 528 | ✗ | unpackWasm(destPath, wasmZips, cachePath); | |
| 529 | ✗ | if not listEmpty(wasmZips) then | |
| 530 | ✗ | wasmDirs := destPath :: wasmDirs; | |
| 531 | end if; | ||
| 532 | ✗ | if oldSha == "" then | |
| 533 | ✗ | Error.addSourceMessage(Error.NOTIFY_PKG_INSTALL_DONE, {pack.sha}, makeSourceInfo(destPathPkgMo)); | |
| 534 | else | ||
| 535 | ✗ | Error.addSourceMessage(Error.NOTIFY_PKG_UPGRADE_DONE, {pack.sha, oldSha}, makeSourceInfo(destPathPkgMo)); | |
| 536 | end if; | ||
| 537 | else | ||
| 538 | ✗ | Error.addMessage(Error.ERROR_PKG_INSTALL_NO_PACKAGE_MO, {curCachePath + System.basename(pack.urlToZipFile), destPathPkgMo}); | |
| 539 | ✗ | System.removeDirectory(destPath); | |
| 540 | ✗ | fail(); | |
| 541 | end if; | ||
| 542 | ✗ | System.writeFile(destPathPkgInfo, JSON.toString(pack.json)+"\n"); | |
| 543 | end for; | ||
| 544 | end installPackage; | ||
| 545 | |||
| 546 | function installCachedPackages | ||
| 547 | "Installs cached libraries from the installation directory if the user's | ||
| 548 | library directory is empty or doesn't exist, to allow bundling libraries with | ||
| 549 | the installation." | ||
| 550 | protected | ||
| 551 | String packageIndex, homeDir; | ||
| 552 | JSON obj, libs_obj, lib_obj, versions_obj; | ||
| 553 | list<String> libs; | ||
| 554 | algorithm | ||
| 555 | 5857 | homeDir := Settings.getHomeDir(runningTestsuite=Testsuite.isRunning()); | |
| 556 |
4/10✓ Branch 2 taken 5855 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 5855 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 5855 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
|
5857 | if not listEmpty(System.subDirectories(getUserLibraryPath())) or homeDir=="" or homeDir=="/" then |
| 557 | // Return if the user's library directory isn't empty, or if we're running | ||
| 558 | // e.g. rtest in which case the path might not be correct but we don't care | ||
| 559 | // and don't want any extra output. | ||
| 560 | 5857 | return; | |
| 561 | end if; | ||
| 562 | |||
| 563 | // Try to fetch the package index from the installation directory. | ||
| 564 | ✗ | packageIndex := getInstallationIndexPath(); | |
| 565 | |||
| 566 | ✗ | if not System.regularFileExists(packageIndex) then | |
| 567 | ✗ | return; | |
| 568 | end if; | ||
| 569 | |||
| 570 | ✗ | obj := JSON.makeNull(); | |
| 571 | |||
| 572 | try | ||
| 573 | ✗ | obj := JSON.parseFile(packageIndex); | |
| 574 | else | ||
| 575 | ✗ | Error.addSourceMessage(Error.ERROR_PKG_INDEX_NOT_PARSED, {packageIndex}, makeSourceInfo(packageIndex)); | |
| 576 | end try; | ||
| 577 | |||
| 578 | // Fetch the names of all the libraries in the package index. | ||
| 579 | try | ||
| 580 | ✗ | libs_obj := JSON.get(obj, "libs"); | |
| 581 | ✗ | libs := JSON.getKeys(libs_obj); | |
| 582 | else | ||
| 583 | ✗ | return; | |
| 584 | end try; | ||
| 585 | |||
| 586 | ✗ | if not listEmpty(libs) then | |
| 587 | ✗ | Error.addSourceMessage(Error.NOTIFY_INITIALIZING_USER_LIBRARIES, {getUserLibraryPath()}, makeSourceInfo(packageIndex)); | |
| 588 | end if; | ||
| 589 | |||
| 590 | // Copy the index to the user library folder to avoid getting errors when | ||
| 591 | // installing the libraries. | ||
| 592 | ✗ | if not System.regularFileExists(getIndexPath()) then | |
| 593 | ✗ | Util.createDirectoryTree(getUserLibraryPath()); | |
| 594 | ✗ | System.copyFile(packageIndex, getIndexPath()); | |
| 595 | end if; | ||
| 596 | |||
| 597 | // Install each version of each library in the package index. | ||
| 598 | ✗ | for lib in libs loop | |
| 599 | ✗ | lib_obj := JSON.get(libs_obj, lib); | |
| 600 | ✗ | versions_obj := JSON.getOrDefault(lib_obj, "versions", JSON.emptyObject()); | |
| 601 | |||
| 602 | ✗ | for version in JSON.getKeys(versions_obj) loop | |
| 603 | ✗ | installPackage(lib, version, true, skipDownload = true); | |
| 604 | end for; | ||
| 605 | end for; | ||
| 606 | |||
| 607 | // Try to download a new package index so the user gets an up to date one. | ||
| 608 | ✗ | updateIndex(); | |
| 609 | end installCachedPackages; | ||
| 610 | |||
| 611 | function installMissingWasmOfLoaded | ||
| 612 | "Fetches the prebuilt wasm modules the index has for loaded libraries the | ||
| 613 | package manager installed without them: before the wasm-jit target was used, | ||
| 614 | or before the index had them. `files` are the loaded top-level classes' files." | ||
| 615 | input list<String> files; | ||
| 616 | input Integer abi; | ||
| 617 | output list<String> wasmDirs = {} "The libraries the modules were unpacked into"; | ||
| 618 | protected | ||
| 619 | list<String> dirs; | ||
| 620 | JSON index, installed, obj; | ||
| 621 | algorithm | ||
| 622 |
1/2✓ Branch 0 taken 2867 times.
✗ Branch 1 not taken.
|
2867 | if abi <= 0 then |
| 623 | 2867 | return; | |
| 624 | end if; | ||
| 625 | ✗ | dirs := list(d for d guard System.regularFileExists(d + "/" + metaDataFileName) in list(System.dirname(f) for f in files)); | |
| 626 | ✗ | if listEmpty(dirs) then | |
| 627 | ✗ | return; | |
| 628 | end if; | ||
| 629 | ✗ | index := getPackageIndex(false); | |
| 630 | // An index from before any wasm modules were built. | ||
| 631 | ✗ | if not JSON.hasKey(index, "systemLibraries") and not Testsuite.isRunning() then | |
| 632 | ✗ | if updateIndex() then | |
| 633 | ✗ | index := getPackageIndex(false); | |
| 634 | end if; | ||
| 635 | end if; | ||
| 636 | ✗ | for dir in dirs loop | |
| 637 | try | ||
| 638 | ✗ | installed := JSON.parseFile(dir + "/" + metaDataFileName); | |
| 639 | ✗ | obj := installedIndexEntry(index, listHead(System.strtok(System.basename(dir), " ")), SemanticVersion.parse(JSON.getString(JSON.get(installed, "version"))), getShaOrZipfile(installed)); | |
| 640 | ✗ | if installMissingWasm(dir, wasmExternalsZips(obj, abi), getCachePath()) then | |
| 641 | wasmDirs := dir :: wasmDirs; | ||
| 642 | end if; | ||
| 643 | else | ||
| 644 | end try; | ||
| 645 | end for; | ||
| 646 | end installMissingWasmOfLoaded; | ||
| 647 | |||
| 648 | function installWasmToolchain | ||
| 649 | "Installs the index's wasm sysroot under ~/.openmodelica/wasm32-wasip1/omc-<generation>, | ||
| 650 | with libc++ for `cxx`, and returns its path, or \"\" if there is none." | ||
| 651 | input Boolean cxx = false; | ||
| 652 | output String sysroot = ""; | ||
| 653 | protected | ||
| 654 | JSON obj; | ||
| 655 | Integer generation; | ||
| 656 | String dir; | ||
| 657 | algorithm | ||
| 658 | try | ||
| 659 | ✗ | obj := JSON.get(getPackageIndex(true), "wasmToolchain"); | |
| 660 | ✗ | JSON.INTEGER(generation) := JSON.get(obj, "generation"); | |
| 661 | else | ||
| 662 | ✗ | Error.addCompilerError("The package index has no wasm toolchain; updatePackageIndex() may fetch a newer index."); | |
| 663 | ✗ | return; | |
| 664 | end try; | ||
| 665 | ✗ | dir := Settings.getHomeDir(Testsuite.isRunning()) + "/.openmodelica/wasm32-wasip1/omc-" + intString(generation) + "/sysroot"; | |
| 666 | ✗ | if not unpackToolchainPart(obj, dir, "/lib/wasm32-wasip1/libc.so") then | |
| 667 | ✗ | return; | |
| 668 | end if; | ||
| 669 | ✗ | if cxx then | |
| 670 | try | ||
| 671 | ✗ | obj := JSON.get(obj, "cxx"); | |
| 672 | else | ||
| 673 | ✗ | Error.addCompilerError("The package index has no libc++ for its wasm toolchain."); | |
| 674 | ✗ | return; | |
| 675 | end try; | ||
| 676 | ✗ | if not unpackToolchainPart(obj, dir, "/lib/wasm32-wasip1/libc++.so") then | |
| 677 | ✗ | return; | |
| 678 | end if; | ||
| 679 | end if; | ||
| 680 | sysroot := dir; | ||
| 681 | end installWasmToolchain; | ||
| 682 | |||
| 683 | protected | ||
| 684 | |||
| 685 | function unpackToolchainPart | ||
| 686 | "Unpacks the zip-file `obj` names into `dir`, unless `marker` shows it is there." | ||
| 687 | input JSON obj; | ||
| 688 | input String dir; | ||
| 689 | input String marker; | ||
| 690 | output Boolean success = true; | ||
| 691 | protected | ||
| 692 | String url, zip, cachePath = getCachePath(); | ||
| 693 | algorithm | ||
| 694 | ✗ | if System.regularFileExists(dir + marker) then | |
| 695 | ✗ | return; | |
| 696 | end if; | ||
| 697 | ✗ | url := JSON.getString(JSON.get(obj, "zipfile")); | |
| 698 | ✗ | zip := cachePath + System.basename(url); | |
| 699 | ✗ | Util.createDirectoryTree(cachePath); | |
| 700 | ✗ | if not System.regularFileExists(zip) then | |
| 701 | ✗ | success := Curl.multiDownload({({url}, zip)}); | |
| 702 | end if; | ||
| 703 | ✗ | if success then | |
| 704 | ✗ | Util.createDirectoryTree(dir); | |
| 705 | ✗ | Unzip.unzipPath(zip, "", dir); | |
| 706 | end if; | ||
| 707 | end unpackToolchainPart; | ||
| 708 | |||
| 709 | function compareUrlBool | ||
| 710 | input tuple<list<String>,String> tpl1, tpl2; | ||
| 711 | output Boolean b; | ||
| 712 | protected | ||
| 713 | String s1, s2; | ||
| 714 | algorithm | ||
| 715 | ✗ | (s1::_,_) := tpl1; | |
| 716 | ✗ | (s2::_,_) := tpl2; | |
| 717 | ✗ | b := stringCompare(s1, s2) > 0; | |
| 718 | end compareUrlBool; | ||
| 719 | |||
| 720 | uniontype PackageInstallInfo | ||
| 721 | record PKG_INSTALL_INFO | ||
| 722 | Boolean needsInstall; | ||
| 723 | String pkg; | ||
| 724 | SemanticVersion.Version version; | ||
| 725 | String urlToZipFile; | ||
| 726 | String path; | ||
| 727 | String sha; | ||
| 728 | Boolean singleFileStructureCopyAllFiles; | ||
| 729 | JSON json; | ||
| 730 | end PKG_INSTALL_INFO; | ||
| 731 | end PackageInstallInfo; | ||
| 732 | |||
| 733 | function installPackageWork | ||
| 734 | input String pkg; | ||
| 735 | input String version; | ||
| 736 | input Boolean exactMatch; | ||
| 737 | input Boolean fallbackOnNonExactMatch; | ||
| 738 | output Boolean success; | ||
| 739 | input output list<PackageInstallInfo> packagesToInstall; | ||
| 740 | protected | ||
| 741 | AvailableLibraries.Tree installedLibraries; | ||
| 742 | VersionMap.Tree installedVersions; | ||
| 743 | list<String> candidates; | ||
| 744 | list<SemanticVersion.Version> candidatesSemver, exactMatches; | ||
| 745 | String versionToInstall, usedVersion, path, sha, jsonPath, zip; | ||
| 746 | SemanticVersion.Version semverToInstall, semver; | ||
| 747 | JSON index, versionObj, versionsObj, usesObj; | ||
| 748 | Boolean indexHasPkg; | ||
| 749 | Option<PackageInstallInfo> packageToInstall = NONE(); | ||
| 750 | algorithm | ||
| 751 | ✗ | candidates := versionsThatProvideTheWanted(pkg, version, printError=true); | |
| 752 | ✗ | candidatesSemver := list(SemanticVersion.parse(candidate) for candidate in candidates); | |
| 753 | ✗ | semver := SemanticVersion.parse(version); | |
| 754 | ✗ | exactMatches := list(candidate for candidate guard 0==SemanticVersion.compare(candidate, semver, compareBuildInformation=SemanticVersion.hasMetaInformation(semver)) in candidatesSemver); | |
| 755 | ✗ | success := false; | |
| 756 | ✗ | for pkgInfo in packagesToInstall loop | |
| 757 | ✗ | if pkgInfo.pkg == pkg then | |
| 758 | ✗ | if SemanticVersion.compare(pkgInfo.version, semver)==0 or max(0==SemanticVersion.compare(pkgInfo.version, candidate) for candidate in candidatesSemver) then | |
| 759 | success := true; | ||
| 760 | ✗ | return; | |
| 761 | end if; | ||
| 762 | ✗ | Error.addMessage(Error.WARNING_PKG_CONFLICTING_VERSIONS, {pkg, SemanticVersion.toString(pkgInfo.version), version}); | |
| 763 | success := true; | ||
| 764 | ✗ | return; | |
| 765 | end if; | ||
| 766 | end for; | ||
| 767 | |||
| 768 | // We know which version we want now. Check with the installed ones | ||
| 769 | ✗ | installedLibraries := getInstalledLibraries(); | |
| 770 | |||
| 771 | ✗ | if listEmpty(candidates) then | |
| 772 | versionToInstall := version; | ||
| 773 | semverToInstall := semver; | ||
| 774 | elseif exactMatch and not listEmpty(exactMatches) then | ||
| 775 | ✗ | semverToInstall := listHead(exactMatches); | |
| 776 | ✗ | versionToInstall := SemanticVersion.toString(semverToInstall); | |
| 777 | else | ||
| 778 | ✗ | versionToInstall := listHead(candidates); | |
| 779 | ✗ | semverToInstall := listHead(candidatesSemver); | |
| 780 | end if; | ||
| 781 | ✗ | index := getPackageIndex(printError=true); | |
| 782 | indexHasPkg := true; | ||
| 783 | ✗ | sha := ""; | |
| 784 | |||
| 785 | ✗ | if AvailableLibraries.hasKey(installedLibraries, pkg) then | |
| 786 | ✗ | installedVersions := AvailableLibraries.get(installedLibraries, pkg); | |
| 787 | ✗ | if VersionMap.hasKey(installedVersions, semverToInstall) or (version=="" and not indexHasPkg) then | |
| 788 | ✗ | success := true; | |
| 789 | ✗ | path := if VersionMap.hasKey(installedVersions, semverToInstall) then VersionMap.get(installedVersions, semverToInstall) else "#DUMMY#"; | |
| 790 | ✗ | jsonPath := path + "/" + metaDataFileName; | |
| 791 | ✗ | if System.regularFileExists(jsonPath) then | |
| 792 | ✗ | versionObj := JSON.parseFile(jsonPath); | |
| 793 | ✗ | zip := JSON.getString(JSON.get(versionObj, "zipfile")); | |
| 794 | try | ||
| 795 | ✗ | sha := JSON.getString(JSON.get(versionObj, "sha")); | |
| 796 | else | ||
| 797 | end try; | ||
| 798 | else | ||
| 799 | zip := ""; | ||
| 800 | end if; | ||
| 801 | ✗ | packageToInstall := SOME(PKG_INSTALL_INFO(false, pkg, semverToInstall, zip, path, sha, false, | |
| 802 | installedIndexEntry(index, pkg, semverToInstall, if sha <> "" then sha else zip))); | ||
| 803 | ✗ | indexHasPkg := JSON.hasKey(JSON.get(index, "libs"), pkg); | |
| 804 | end if; | ||
| 805 | end if; | ||
| 806 | ✗ | if not success then | |
| 807 | ✗ | if listEmpty(candidates) then | |
| 808 | ✗ | Error.addSourceMessage(Error.ERROR_PKG_NOT_FOUND_VERSION, {pkg, version, stringDelimitList(getAllProvidedVersionsForLibrary(pkg, true),"\n")}, makeSourceInfo(getIndexPath())); | |
| 809 | ✗ | return; | |
| 810 | end if; | ||
| 811 | |||
| 812 | ✗ | if exactMatch and not max(0==SemanticVersion.compare(semver, candidate) for candidate in candidatesSemver) then | |
| 813 | ✗ | if not fallbackOnNonExactMatch then | |
| 814 | ✗ | Error.addSourceMessage(Error.ERROR_PKG_NOT_EXACT_MATCH, {pkg, version, stringDelimitList(candidates, ", ")}, makeSourceInfo(getIndexPath())); | |
| 815 | ✗ | return; | |
| 816 | end if; | ||
| 817 | ✗ | versionToInstall := listHead(candidates); | |
| 818 | ✗ | semverToInstall := listHead(candidatesSemver); | |
| 819 | end if; | ||
| 820 | end if; | ||
| 821 | |||
| 822 | ✗ | if not indexHasPkg then | |
| 823 | ✗ | packagesToInstall := Util.getOption(packageToInstall) :: packagesToInstall; | |
| 824 | ✗ | return; | |
| 825 | end if; | ||
| 826 | |||
| 827 | ✗ | versionsObj := JSON.get(JSON.get(JSON.get(index, "libs"), pkg), "versions"); | |
| 828 | ✗ | if success and not JSON.hasKey(versionsObj, versionToInstall) then | |
| 829 | ✗ | packagesToInstall := Util.getOption(packageToInstall) :: packagesToInstall; | |
| 830 | ✗ | return; | |
| 831 | end if; | ||
| 832 | ✗ | versionObj := JSON.get(versionsObj, versionToInstall); | |
| 833 | |||
| 834 | ✗ | if (not success) or (sha <> "" and sha <> getShaOrZipfile(versionObj)) then | |
| 835 | ✗ | success := true; | |
| 836 | ✗ | packageToInstall := SOME(PKG_INSTALL_INFO(true, pkg, semverToInstall, JSON.getString(JSON.get(versionObj, "zipfile")), JSON.getString(JSON.get(versionObj, "path")), getShaOrZipfile(versionObj), JSON.getBoolean(JSON.getOrDefault(versionObj, "singleFileStructureCopyAllFiles", JSON.FALSE())), versionObj)); | |
| 837 | end if; | ||
| 838 | |||
| 839 | ✗ | usesObj := JSON.getOrDefault(versionObj, "uses", JSON.emptyObject()); | |
| 840 | |||
| 841 | ✗ | packagesToInstall := Util.getOption(packageToInstall) :: packagesToInstall; | |
| 842 | |||
| 843 | ✗ | for usesPackage in JSON.getKeys(usesObj) loop | |
| 844 | ✗ | JSON.STRING(usedVersion) := JSON.get(usesObj, usesPackage); | |
| 845 | ✗ | (success, packagesToInstall) := installPackageWork(usesPackage, usedVersion, exactMatch, true, packagesToInstall); | |
| 846 | ✗ | if not success then | |
| 847 | ✗ | return; | |
| 848 | end if; | ||
| 849 | end for; | ||
| 850 | end installPackageWork; | ||
| 851 | |||
| 852 | function installedIndexEntry | ||
| 853 | "The index's entry for an installed package version, if it is still built from | ||
| 854 | the same sources (`shaOrZip`); its prebuilt wasm modules are only good then." | ||
| 855 | input JSON index; | ||
| 856 | input String pkg; | ||
| 857 | input SemanticVersion.Version version; | ||
| 858 | input String shaOrZip; | ||
| 859 | output JSON obj = JSON.emptyObject(); | ||
| 860 | protected | ||
| 861 | JSON o; | ||
| 862 | algorithm | ||
| 863 | try | ||
| 864 | ✗ | o := JSON.get(JSON.get(JSON.get(JSON.get(index, "libs"), pkg), "versions"), SemanticVersion.toString(version)); | |
| 865 | // A package installed from an index without shas only knows its zipfile. | ||
| 866 | ✗ | if shaOrZip <> "" and (getShaOrZipfile(o) == shaOrZip or JSON.hasKey(o, "zipfile") and System.basename(JSON.getString(JSON.get(o, "zipfile"))) == shaOrZip) then | |
| 867 | obj := o; | ||
| 868 | end if; | ||
| 869 | else | ||
| 870 | end try; | ||
| 871 | end installedIndexEntry; | ||
| 872 | |||
| 873 | function installMissingWasm | ||
| 874 | "Fetches and unpacks the prebuilt wasm modules of an installed package that it | ||
| 875 | does not have yet: those of a newer generation, or a rebuild of one." | ||
| 876 | input String destPath; | ||
| 877 | input list<tuple<String,String>> zips; | ||
| 878 | input String cachePath; | ||
| 879 | output Boolean unpacked = false; | ||
| 880 | protected | ||
| 881 | list<tuple<String,String>> missing; | ||
| 882 | String lock = destPath + "/Resources/Library/wasm32-wasip1.lock"; | ||
| 883 | algorithm | ||
| 884 | ✗ | missing := list(z for z guard not wasmUnpacked(destPath, z) in zips); | |
| 885 | ✗ | if listEmpty(missing) then | |
| 886 | ✗ | return; | |
| 887 | end if; | ||
| 888 | // Another omc is unpacking them; this one goes on with what is there. | ||
| 889 | ✗ | if not wasmLock(lock) then | |
| 890 | ✗ | return; | |
| 891 | end if; | ||
| 892 | ✗ | missing := list(z for z guard not wasmUnpacked(destPath, z) in zips); | |
| 893 | // The library's own zip unpacks into the generation directory that holds the | ||
| 894 | // system libraries' ones, so replacing it replaces them all. | ||
| 895 | ✗ | if List.any(missing, wasmIsGenerationZip) then | |
| 896 | missing := zips; | ||
| 897 | end if; | ||
| 898 | try | ||
| 899 | ✗ | Util.createDirectoryTree(cachePath); | |
| 900 | ✗ | if not listEmpty(missing) and Curl.multiDownload(list(({Util.tuple21(z)}, cachePath + System.basename(Util.tuple21(z))) for z guard not System.regularFileExists(cachePath + System.basename(Util.tuple21(z))) in missing)) then | |
| 901 | ✗ | unpackWasm(destPath, missing, cachePath); | |
| 902 | unpacked := true; | ||
| 903 | end if; | ||
| 904 | else | ||
| 905 | ✗ | System.removeDirectory(lock); | |
| 906 | ✗ | fail(); | |
| 907 | end try; | ||
| 908 | ✗ | System.removeDirectory(lock); | |
| 909 | end installMissingWasm; | ||
| 910 | |||
| 911 | function wasmLock | ||
| 912 | "Takes the lock directory `lock`, unless another omc holds it. Renaming a | ||
| 913 | non-empty directory onto an existing one fails, which makes it exclusive. A | ||
| 914 | lock older than ten minutes was left by an omc that died and is taken over." | ||
| 915 | input String lock; | ||
| 916 | output Boolean locked; | ||
| 917 | protected | ||
| 918 | String tmp; | ||
| 919 | Option<Real> t; | ||
| 920 | algorithm | ||
| 921 | ✗ | Util.createDirectoryTree(System.dirname(lock)); | |
| 922 | ✗ | tmp := System.createTemporaryDirectory(lock + "."); | |
| 923 | ✗ | System.writeFile(tmp + "/owner", ""); | |
| 924 | ✗ | locked := System.rename(tmp, lock); | |
| 925 | ✗ | if not locked then | |
| 926 | ✗ | t := System.getFileModificationTime(lock); | |
| 927 | ✗ | if isSome(t) and System.getCurrentTime() - Util.getOption(t) > 600 then | |
| 928 | ✗ | System.removeDirectory(lock); | |
| 929 | ✗ | locked := System.rename(tmp, lock); | |
| 930 | end if; | ||
| 931 | end if; | ||
| 932 | ✗ | if not locked then | |
| 933 | ✗ | System.removeDirectory(tmp); | |
| 934 | end if; | ||
| 935 | end wasmLock; | ||
| 936 | |||
| 937 | constant String wasmZipMarker = ".omc-zipfile" "Names the zip a directory of wasm modules was unpacked from"; | ||
| 938 | |||
| 939 | function wasmIsGenerationZip | ||
| 940 | input tuple<String,String> zip; | ||
| 941 | output Boolean b = listLength(System.strtok(Util.tuple22(zip), "/")) == 1; | ||
| 942 | end wasmIsGenerationZip; | ||
| 943 | |||
| 944 | function wasmUnpacked | ||
| 945 | input String destPath; | ||
| 946 | input tuple<String,String> zip; | ||
| 947 | output Boolean b; | ||
| 948 | protected | ||
| 949 | String marker = destPath + "/Resources/Library/wasm32-wasip1/" + Util.tuple22(zip) + "/" + wasmZipMarker; | ||
| 950 | algorithm | ||
| 951 | ✗ | b := System.regularFileExists(marker) and System.readFile(marker) == System.basename(Util.tuple21(zip)); | |
| 952 | end wasmUnpacked; | ||
| 953 | |||
| 954 | function unpackWasm | ||
| 955 | "Unpacks zips of prebuilt wasm modules into Resources/Library/wasm32-wasip1. | ||
| 956 | A generation's system libraries go into its tree before it is renamed into | ||
| 957 | place, so an omc running at the same time never sees it half written." | ||
| 958 | input String destPath; | ||
| 959 | input list<tuple<String,String>> zips; | ||
| 960 | input String cachePath; | ||
| 961 | protected | ||
| 962 | String base = destPath + "/Resources/Library/wasm32-wasip1/"; | ||
| 963 | list<tuple<String,String>> gens, libs; | ||
| 964 | algorithm | ||
| 965 | ✗ | (gens, libs) := List.splitOnTrue(zips, wasmIsGenerationZip); | |
| 966 | ✗ | for g in gens loop | |
| 967 | ✗ | unpackWasmTree(base + Util.tuple22(g), g :: list(z for z guard System.dirname(Util.tuple22(z)) == Util.tuple22(g) in libs), cachePath); | |
| 968 | ✗ | libs := list(z for z guard System.dirname(Util.tuple22(z)) <> Util.tuple22(g) in libs); | |
| 969 | end for; | ||
| 970 | ✗ | for z in libs loop | |
| 971 | ✗ | unpackWasmTree(base + Util.tuple22(z), {z}, cachePath); | |
| 972 | end for; | ||
| 973 | end unpackWasm; | ||
| 974 | |||
| 975 | function unpackWasmTree | ||
| 976 | "Replaces `dir` with the first zip's contents and each other's in a | ||
| 977 | subdirectory named after it, built beside `dir` and renamed into place." | ||
| 978 | input String dir; | ||
| 979 | input list<tuple<String,String>> zips; | ||
| 980 | input String cachePath; | ||
| 981 | protected | ||
| 982 | String tmp, sub; | ||
| 983 | Boolean first = true; | ||
| 984 | algorithm | ||
| 985 | ✗ | Util.createDirectoryTree(System.dirname(dir)); | |
| 986 | ✗ | tmp := System.createTemporaryDirectory(dir + ".tmp"); | |
| 987 | ✗ | for z in zips loop | |
| 988 | ✗ | sub := if first then tmp else tmp + "/" + System.basename(Util.tuple22(z)); | |
| 989 | first := false; | ||
| 990 | ✗ | Util.createDirectoryTree(sub); | |
| 991 | ✗ | Unzip.unzipPath(cachePath + System.basename(Util.tuple21(z)), "", sub); | |
| 992 | ✗ | System.writeFile(sub + "/" + wasmZipMarker, System.basename(Util.tuple21(z))); | |
| 993 | end for; | ||
| 994 | ✗ | System.removeDirectory(dir); | |
| 995 | ✗ | if not System.rename(tmp, dir) then | |
| 996 | // Another omc put its copy in place first. | ||
| 997 | ✗ | System.removeDirectory(tmp); | |
| 998 | end if; | ||
| 999 | end unpackWasmTree; | ||
| 1000 | |||
| 1001 | function wasmExternalsZips | ||
| 1002 | "The zip-files with the prebuilt wasm external \"C\" modules of a package | ||
| 1003 | version, each with the directory under Resources/Library/wasm32-wasip1 it | ||
| 1004 | unpacks into: the library's own bundle, then the system libraries it needs, | ||
| 1005 | all under the generation they were built in. Older generations stay." | ||
| 1006 | input JSON versionObj; | ||
| 1007 | input Integer abi; | ||
| 1008 | output list<tuple<String,String>> zips = {}; | ||
| 1009 | protected | ||
| 1010 | JSON obj, systemLibraries; | ||
| 1011 | Integer generation; | ||
| 1012 | algorithm | ||
| 1013 | ✗ | if abi <= 0 then | |
| 1014 | ✗ | return; | |
| 1015 | end if; | ||
| 1016 | try | ||
| 1017 | ✗ | obj := JSON.get(JSON.get(versionObj, "wasm"), intString(abi)); | |
| 1018 | ✗ | JSON.INTEGER(generation) := JSON.get(obj, "generation"); | |
| 1019 | ✗ | zips := {(JSON.getString(JSON.get(obj, "zipfile")), "omc-" + intString(generation))}; | |
| 1020 | else | ||
| 1021 | ✗ | return; | |
| 1022 | end try; | ||
| 1023 | ✗ | systemLibraries := JSON.getOrDefault(getPackageIndex(false), "systemLibraries", JSON.emptyObject()); | |
| 1024 | ✗ | for name in JSON.getStringList(JSON.getOrDefault(obj, "systemLibraries", JSON.emptyArray())) loop | |
| 1025 | try | ||
| 1026 | ✗ | zips := (JSON.getString(JSON.get(JSON.get(JSON.get(JSON.get(systemLibraries, name), "wasm"), intString(abi)), "zipfile")), "omc-" + intString(generation) + "/" + name) :: zips; | |
| 1027 | else | ||
| 1028 | ✗ | Error.addCompilerWarning("The package index has no wasm system library " + name + " for ABI " + intString(abi) + "."); | |
| 1029 | end try; | ||
| 1030 | end for; | ||
| 1031 | ✗ | zips := listReverse(zips); | |
| 1032 | end wasmExternalsZips; | ||
| 1033 | |||
| 1034 | function getShaOrZipfile | ||
| 1035 | input JSON obj; | ||
| 1036 | output String res; | ||
| 1037 | algorithm | ||
| 1038 | ✗ | res := if JSON.hasKey(obj, "sha") then JSON.getString(JSON.get(obj, "sha")) else System.basename(JSON.getString(JSON.get(obj, "zipfile"))); | |
| 1039 | end getShaOrZipfile; | ||
| 1040 | |||
| 1041 | function getAllUrls | ||
| 1042 | input String url; | ||
| 1043 | input list<String> mirrors; | ||
| 1044 | output list<String> urls; | ||
| 1045 | protected | ||
| 1046 | String urlWithoutProtocol, newUrl; | ||
| 1047 | algorithm | ||
| 1048 | urls := {url}; | ||
| 1049 | ✗ | if not StringUtil.startsWith(url, "https://") then | |
| 1050 | ✗ | return; | |
| 1051 | end if; | ||
| 1052 | ✗ | urlWithoutProtocol := substring(url,9,stringLength(url)); | |
| 1053 | ✗ | for mirror in mirrors loop | |
| 1054 | ✗ | newUrl := if StringUtil.endsWith(mirror, "/") then (mirror + urlWithoutProtocol) else (mirror + "/" + urlWithoutProtocol); | |
| 1055 | urls := newUrl::urls; | ||
| 1056 | end for; | ||
| 1057 | end getAllUrls; | ||
| 1058 | |||
| 1059 | function getMirrors | ||
| 1060 | output list<String> mirrors; | ||
| 1061 | protected | ||
| 1062 | JSON obj; | ||
| 1063 | algorithm | ||
| 1064 | ✗ | obj := getPackageIndex(false); | |
| 1065 | ✗ | if not JSON.hasKey(obj, "mirrors") then | |
| 1066 | mirrors := {}; | ||
| 1067 | ✗ | return; | |
| 1068 | end if; | ||
| 1069 | ✗ | obj := JSON.get(obj, "mirrors"); | |
| 1070 | ✗ | mirrors := JSON.getStringList(obj); | |
| 1071 | end getMirrors; | ||
| 1072 | |||
| 1073 | function getUserLibraryPath | ||
| 1074 | output String path; | ||
| 1075 | algorithm | ||
| 1076 | 8108 | path := Settings.getHomeDir(Testsuite.isRunning()) + "/.openmodelica/libraries/"; | |
| 1077 | end getUserLibraryPath; | ||
| 1078 | |||
| 1079 | function getIndexPath | ||
| 1080 | output String path; | ||
| 1081 | algorithm | ||
| 1082 | ✗ | path := Settings.getHomeDir(Testsuite.isRunning()) + "/.openmodelica/libraries/index.json"; | |
| 1083 | end getIndexPath; | ||
| 1084 | |||
| 1085 | public | ||
| 1086 | function getCachePath | ||
| 1087 | output String path; | ||
| 1088 | algorithm | ||
| 1089 | ✗ | path := Settings.getHomeDir(Testsuite.isRunning()) + "/.openmodelica/cache/"; | |
| 1090 | end getCachePath; | ||
| 1091 | |||
| 1092 | protected | ||
| 1093 | |||
| 1094 | function getInstallationIndexPath | ||
| 1095 | output String path; | ||
| 1096 | algorithm | ||
| 1097 | ✗ | path := Settings.getInstallationDirectoryPath() + "/share/omlibrary/cache/index.json"; | |
| 1098 | end getInstallationIndexPath; | ||
| 1099 | |||
| 1100 | function getInstallationCachePath | ||
| 1101 | output String path; | ||
| 1102 | algorithm | ||
| 1103 | ✗ | path := Settings.getInstallationDirectoryPath() + "/share/omlibrary/cache/"; | |
| 1104 | end getInstallationCachePath; | ||
| 1105 | |||
| 1106 | function makeSourceInfo | ||
| 1107 | input String fileName; | ||
| 1108 | output SourceInfo info; | ||
| 1109 | algorithm | ||
| 1110 | ✗ | info := SOURCEINFO(fileName, true, 0, 0, 0, 0, 0.0); | |
| 1111 | end makeSourceInfo; | ||
| 1112 | |||
| 1113 | annotation(__OpenModelica_Interface="script_util"); | ||
| 1114 | end PackageManagement; | ||
| 1115 |