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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
#!/usr/bin/env python3
# Find all crates in old index format (single file per crate) and generate one
# file per release.
#
# The update is done in place and for all indexes found under the current
# directory.
import os
import re
import rtoml
from copy import deepcopy
from pathlib import Path
FROM_VERSION = "0.3"
INTO_VERSION = {"version": "0.4"}
def is_index(path):
# Look for an "index.toml" file that contains a matching 'version = "x.x"'
target = os.path.join(path, "index.toml")
if not os.path.isfile(target):
return False
with open(target) as file:
try:
contents = rtoml.load(file)
if "version" in contents:
version = contents["version"]
if version == FROM_VERSION:
return True
else:
print(f"Version mismatch: {path} version is {version}, expected {FROM_VERSION}")
return False
else:
print(f"Malformed index file: no version found inside {path}")
except rtoml.TomlParsingError:
print(f"Not a target: {target} failed to load as TOML")
raise
def fix_order(obj):
delayed = dict()
# extract tables/table arrays:
for key in obj:
val = obj[key]
if isinstance(val, dict) or \
(isinstance(val, list) and len(val) > 0 and isinstance(val[0], dict)):
delayed[key] = val
# reinsert at last position
for key in delayed:
obj.pop(key)
obj[key] = delayed[key]
def write_externals(crate_dirname, name, general, external):
print(f" Writing externals for {name}...")
crate = deepcopy(general)
crate["name"] = name
crate["external"] = external
fix_order(crate)
with open(os.path.join(crate_dirname, name + "-external.toml"), "wt") as file:
rtoml.dump(crate, file)
def write_release(crate_dirname, name, general, version, release):
print(f" Writing release {name}={version}...")
crate = deepcopy(general)
crate["name"] = name
crate["version"] = version
# Ensure proper merging
for key in release:
if key in crate:
if isinstance(crate[key], dict) and isinstance(release[key], dict):
# Merge these dicts:
crate[key].update(release[key])
elif isinstance(crate[key], list) and isinstance(release[key], list):
# Merge arrays
crate[key] += release[key]
else:
raise RuntimeError(f"Key {key} is both in general and release sections in {crate_dirname} {version}")
else:
crate[key] = release[key]
# Fix relative origins which now are one level deeper than before
if "origin" in crate and "../" in crate["origin"]:
crate['origin'] = crate['origin'].replace("../", "../../", 1)
# Fix dictionary ordering to ensure atoms (or arrays of atoms) are before tables/arrays of tables. Otherwise, rtoml
# does not do it for us, and we end with an invalid TOML serialization order (top-level atoms must come before any
# tables/arrays).
fix_order(crate)
# Fix `depends-on` to be an array. No matter that dependencies are grouped.
if "depends-on" in crate:
deps = crate.pop("depends-on")
crate["depends-on"] = [deps]
with open(os.path.join(crate_dirname, name + f"-{version}.toml"), "wt") as file:
rtoml.dump(crate, file)
def fix_ver(version):
# Ensure the version has major.minor.patch numbers, for regularity, and that pre-release/build info is kept
semver = r"^(?P<major>0|[1-9]\d*)(?:\.(?P<minor>0|[1-9]\d*))?(?:\.(?P<patch>0|[1-9]\d*))?" \
r"(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|" \
r"\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
parts = re.match(semver, version).groupdict()
return parts["major"] + \
(("." + parts["minor"]) if parts["minor"] is not None else ".0") + \
(("." + parts["patch"]) if parts["patch"] is not None else ".0") + \
(("-" + parts["prerelease"]) if parts["prerelease"] is not None else "") + \
(("+" + parts["buildmetadata"]) if parts["buildmetadata"] is not None else "")
def split_crate(parent_dir, crate_file):
# crate_file is an os.DirEntry that points to an old-style merged manifests file
print(f" Splitting {crate_file.path}...")
try:
crates = rtoml.load(Path(crate_file.path))
name = crate_file.name.split('.')[0]
crate_dirname = os.path.join(parent_dir, name)
if not os.path.isdir(crate_dirname):
os.mkdir(crate_dirname)
general = crates.pop("general")
if "external" in crates:
write_externals(crate_dirname, name, general, crates.pop("external"))
# remaining top-level tables must be versions (general was already pop'd)
for version in crates:
write_release(crate_dirname, name, general, fix_ver(version), crates[version])
# Clean up unneeded merged manifest
os.remove(crate_file)
except rtoml.TomlParsingError as ex:
print(f"FAILED to migrate {crate_file}: " + str(ex))
raise
def split_contents(path):
# Path points to a two-letter folder containing merged manifests
with os.scandir(path) as contents:
for entry in contents:
if entry.name.endswith(".toml"):
split_crate(path, entry)
else:
print(f"EXTRANEOUS entry looking for manifests: {entry.path}")
def migrate(path):
print(f"Migrating {path}...")
# All two-letter folders will contain crates...
with os.scandir(path) as contents:
for entry in contents:
if len(entry.name) == 2 and entry.is_dir():
split_contents(entry.path)
elif entry.name != "index.toml":
print(f"EXTRANEOUS entry looking for shelves: {entry.path}")
# Finalize by updating the index version
with open(os.path.join(path, "index.toml"), "wt") as file:
rtoml.dump(INTO_VERSION, file)
def traverse(path):
if is_index(path):
migrate(path)
else:
# keep looking for nested indexes
with os.scandir(path) as iterate:
for child in iterate:
if child.is_dir():
traverse(child.path)
traverse(os.getcwd())
|