mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-04 04:27:34 +00:00
decided that changing versions of the underlying crates has no actual meaning with regards to the release process, so, leaving this decoupled.
393 lines
17 KiB
Python
393 lines
17 KiB
Python
#! /usr/bin/env python3
|
|
import argparse
|
|
from pathlib import Path
|
|
import re
|
|
import subprocess
|
|
import time
|
|
from enum import Enum
|
|
|
|
class SemverBump(Enum):
|
|
REVISION = 'rev'
|
|
MINOR = 'minor'
|
|
MAJOR = 'major'
|
|
|
|
# format is [crate : path]
|
|
# this is an ordered list that also prescribes the publication order to crates.io
|
|
UTRA_CRATES = [
|
|
["svd2utra", "svd2utra"],
|
|
["utralib", "utralib"],
|
|
]
|
|
CRATES = [
|
|
["xous", "xous-rs"],
|
|
["xous-ipc", "xous-ipc"],
|
|
["xous-api-log", "api/xous-api-log"],
|
|
["xous-api-names", "api/xous-api-names"],
|
|
["xous-api-ticktimer", "api/xous-api-ticktimer"],
|
|
|
|
# ["xous-api-susres", "api/xous-api-susres"],
|
|
# ["xous-kernel", "kernel"], # this is no longer published, as it is an implementation
|
|
# ["xous-log", "services/xous-log"], # implementations, no longer published
|
|
# ["xous-names", "services/xous-names"],
|
|
# ["xous-susres", "services/xous-susres"],
|
|
# ["xous-ticktimer", "services/xous-ticktimer"],
|
|
]
|
|
# dictionary of crate names -> version strings
|
|
VERSIONS = {}
|
|
|
|
class PatchInfo:
|
|
def __init__(self, filename, cratelist=None, cratename=None):
|
|
self.filepath = Path(filename)
|
|
if not self.filepath.is_file():
|
|
print("Bad crate path: {}".format(filename))
|
|
self.cratename = cratename
|
|
self.cratelist = cratelist
|
|
self.debug = False
|
|
|
|
def get_version(self):
|
|
with open(self.filepath, 'r') as file:
|
|
lines = file.readlines()
|
|
in_package = False
|
|
name_check = False
|
|
for line in lines:
|
|
if line.strip().startswith('['):
|
|
if 'package' in line:
|
|
in_package = True
|
|
else:
|
|
in_package = False
|
|
continue
|
|
elif in_package:
|
|
if line.strip().startswith('version'):
|
|
version = line.split('=')[1].replace('"', '').strip()
|
|
if line.strip().startswith('name'):
|
|
name = line.split('=')[1].replace('"', '').strip()
|
|
for [item_name, path] in self.cratelist:
|
|
if name == item_name:
|
|
name_check = True
|
|
if name_check:
|
|
assert version is not None # "Target name found but no version was extracted!"
|
|
VERSIONS[name] = version
|
|
|
|
return name_check
|
|
|
|
def debug_mode(self, arg):
|
|
self.debug = arg
|
|
|
|
def output(self, line):
|
|
if self.debug:
|
|
print("Dry run: {}".format(line.rstrip()))
|
|
# pass
|
|
else:
|
|
self.file.write(line)
|
|
|
|
# assumes that VERSIONS has been initialized.
|
|
def increment_versions(self, mode='bump', which=SemverBump.REVISION):
|
|
# check that global variables are in sane states
|
|
assert len(VERSIONS) > 0 # "No VERSIONS found, something is weird."
|
|
with open(self.filepath, 'r') as file:
|
|
lines = file.readlines()
|
|
with open(self.filepath, 'w', newline='\n') as file:
|
|
self.file = file
|
|
in_package = False
|
|
in_dependencies = False
|
|
for line in lines:
|
|
if line.strip().startswith('['):
|
|
if 'package' in line:
|
|
in_package = True
|
|
else:
|
|
in_package = False
|
|
if 'dependencies' in line:
|
|
in_dependencies = True
|
|
else:
|
|
in_dependencies = False
|
|
self.output(line)
|
|
elif line.strip().startswith('#'): # skip comments
|
|
self.output(line)
|
|
elif in_package:
|
|
# increment my own version, if I'm in the listed crates and we're in 'bump' mode
|
|
if (self.cratename is not None) and (mode == 'bump'):
|
|
if line.strip().startswith('version'):
|
|
self.output('version = "{}"\n'.format(bump_version(VERSIONS[self.cratename], which)))
|
|
else:
|
|
self.output(line)
|
|
else:
|
|
self.output(line)
|
|
|
|
if line.strip().startswith('name'):
|
|
this_crate = line.split('=')[1].replace('"', '').strip()
|
|
print("Patching {}...".format(this_crate))
|
|
elif in_dependencies:
|
|
# now increment dependency versions
|
|
|
|
# first search and see if the dependency name is in the table
|
|
if 'package' in line: # renamed package
|
|
semiparse = re.split('=|,', line.strip())
|
|
if 'package' in semiparse[0]:
|
|
# catch the case that the crate name has the word package in it. If this error gets printed,
|
|
# we have to rewrite this semi-parser to be smarter about looking inside the curly braces only,
|
|
# instead of just stupidly splitting on = and ,
|
|
print("Warning: crate name has the word 'package' in it, and we don't parse this correctly!")
|
|
print("Searching in {}, found {}".format(this_crate, semiparse[0]))
|
|
else:
|
|
# extract the first index where 'package' is found
|
|
index = [idx for idx, s in enumerate(semiparse) if 'package' in s][0]
|
|
depcrate = semiparse[index + 1].replace('"', '').strip()
|
|
# print("assigned package name: {}".format(depcrate))
|
|
else: # simple version number
|
|
depcrate = line.strip().split('=')[0].strip()
|
|
# print("simple package name: {}".format(depcrate))
|
|
|
|
# print("{}:{}".format(this_crate, depcrate))
|
|
if depcrate in VERSIONS:
|
|
if mode == 'bump':
|
|
oldver = VERSIONS[depcrate]
|
|
(newline, numsubs) = re.subn(oldver, bump_version(oldver, which), line)
|
|
if numsubs != 1 and not "\"*\"" in newline:
|
|
print("Warning! Version substitution failed for {}:{} in crate {} ({})".format(depcrate, oldver, this_crate, numsubs))
|
|
|
|
# print("orig: {}\nnew: {}".format(line, newline))
|
|
self.output(newline)
|
|
elif mode == 'to_local':
|
|
if 'path' in line:
|
|
self.output(line) # already local path, do nothing
|
|
else:
|
|
for [name, path] in self.cratelist:
|
|
if depcrate == name:
|
|
# print("self.file: {}".format(self.file.name))
|
|
depth = self.file.name.count('/')
|
|
base = '../' * (depth)
|
|
subpath = 'path = "{}{}"'.format(base, path)
|
|
if subpath is None:
|
|
print("Error: couldn't find substitution path for dependency {}".format(depcrate))
|
|
|
|
if 'version' in line:
|
|
oldver = 'version = "{}"'.format(VERSIONS[depcrate])
|
|
newpath = subpath
|
|
else:
|
|
oldver = '"{}"'.format(VERSIONS[depcrate])
|
|
newpath = '{{ {} }}'.format(subpath)
|
|
|
|
(newline, numsubs) = re.subn(oldver, newpath, line)
|
|
if numsubs != 1 and not "\"*\"" in newline:
|
|
print("Warning! Path substitution failed for {}:{} in crate {} ({})".format(depcrate, oldver, this_crate, numsubs))
|
|
|
|
self.output(newline)
|
|
elif mode == 'to_remote':
|
|
if 'path' not in line:
|
|
self.output(line) # already remote, nothing to do
|
|
else:
|
|
if '{' in line and ',' in line:
|
|
specs = re.split(',|}|{', line) # line.split(',')
|
|
for spec in specs:
|
|
if 'path' in spec:
|
|
oldpath = spec.rstrip().lstrip()
|
|
if oldpath is None:
|
|
print("Error! couldn't parse out path to substitute for dependency {}".format(depcrate))
|
|
newver = 'version = "{}"'.format(VERSIONS[depcrate])
|
|
(newline, numsubs) = re.subn(oldpath, newver, line)
|
|
if numsubs != 1 and not "\"*\"" in newline:
|
|
print("Warning! Path substitution failed for {}:{} in crate {} ({})".format(depcrate, oldpath, this_crate, numsubs))
|
|
else:
|
|
# print("Substitute {}:{} in crate {} ({})".format(depcrate, oldpath, this_crate, newver))
|
|
# print(" " + oldpath)
|
|
# print(" " + newline)
|
|
pass
|
|
self.output(newline)
|
|
else:
|
|
self.output('{} = "{}"\n'.format(depcrate, VERSIONS[depcrate]))
|
|
else:
|
|
self.output(line)
|
|
else:
|
|
self.output(line)
|
|
# if debug mode, just write the line unharmed
|
|
if self.debug:
|
|
self.file.write(line)
|
|
|
|
def bump_version(semver, which: SemverBump):
|
|
components = semver.split('.')
|
|
if which == SemverBump.REVISION:
|
|
index = -1
|
|
elif which == SemverBump.MINOR:
|
|
index = -2
|
|
components[-1] = "0"
|
|
elif which == SemverBump.MAJOR:
|
|
index = -3
|
|
components[-1] = "0"
|
|
components[-2] = "0"
|
|
else:
|
|
print("Invalid version bump request")
|
|
exit(-1)
|
|
components[index] = str(int(components[index]) + 1)
|
|
retver = ""
|
|
for (index, component) in enumerate(components):
|
|
retver += str(component)
|
|
if index < len(components) - 1:
|
|
retver += "."
|
|
return retver
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Update and publish crates")
|
|
parser.add_argument(
|
|
"-x", "--xous", help="Process Xous kernel dependent crates", action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"-u", "--utralib", help="Process UTRA dependent crates", action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"-b", "--bump", help="Do a revision version bump", action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"-m", "--minor-bump", help="Do a minor revision bump", action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"-M", "--major-bump", help="Do a major revision bump", action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"-p", "--publish", help="Publish crates", action="store_true",
|
|
)
|
|
parser.add_argument(
|
|
"-l", "--local-paths", help="Convert crate references to local paths", action="store_true"
|
|
)
|
|
parser.add_argument(
|
|
"-r", "--remote-paths", help="Convert crate references to remote paths", action="store_true"
|
|
)
|
|
parser.add_argument(
|
|
"-w", "--wet-run", help="Used in conjunction with --publish to do a 'wet run'", action="store_true"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
bump_check = 0
|
|
if args.bump:
|
|
bump_check += 1
|
|
if args.minor_bump:
|
|
bump_check += 1
|
|
if args.major_bump:
|
|
bump_check += 1
|
|
if bump_check > 1:
|
|
print("Only one of --bump, --minor_bump, or --major-bump may be active at one time")
|
|
exit(1)
|
|
|
|
if args.bump:
|
|
which = SemverBump.REVISION
|
|
elif args.minor_bump:
|
|
which = SemverBump.MINOR
|
|
elif args.major_bump:
|
|
which = SemverBump.MAJOR
|
|
else:
|
|
which = None
|
|
|
|
if not(args.xous or args.utralib):
|
|
print("Warning: no dependencies selected, operation is a no-op. Use -x/-u/... to select dependency trees")
|
|
exit(1)
|
|
|
|
cratelist = []
|
|
if args.utralib: # ordering is important, the UTRA crates need to publish before Xous crates
|
|
cratelist += UTRA_CRATES
|
|
if not args.xous: # most Xous crates are also affected by this, so they need a bump as well
|
|
cratelist += CRATES
|
|
if args.xous:
|
|
cratelist += CRATES
|
|
|
|
if ((args.bump or args.minor_bump or args.major_bump) or args.publish) and (args.local_paths or args.remote_paths):
|
|
print("Do not mix path changes with bump and publish operations. Do them serially.")
|
|
exit(1)
|
|
if args.local_paths and args.remote_paths:
|
|
print("Can't simultaneously change to local and remote paths. Pick only one operation.")
|
|
exit(1)
|
|
|
|
crate_roots = ['.', '../hashes/sha2', '../curve25519-dalek/curve25519-dalek']
|
|
|
|
if args.bump or args.minor_bump or args.major_bump or args.local_paths or args.remote_paths:
|
|
cargo_toml_paths = []
|
|
for roots in crate_roots:
|
|
for path in Path(roots).rglob('Cargo.toml'):
|
|
if 'target' not in str(path):
|
|
not_core_path = True
|
|
for cratespec in cratelist:
|
|
editpath = cratespec[1]
|
|
normpath = str(Path(path)).replace('\\', '/').rpartition('/')[0] # fix windows paths
|
|
# print(editpath)
|
|
# print(normpath)
|
|
if editpath == normpath:
|
|
not_core_path = False
|
|
if not_core_path:
|
|
cargo_toml_paths += [path]
|
|
|
|
# import pprint
|
|
# pp = pprint.PrettyPrinter(indent=2)
|
|
# pp.pprint(cargo_toml_paths)
|
|
|
|
patches = []
|
|
# extract the versions of crates to patch
|
|
for [crate, path] in cratelist:
|
|
# print("extracting {}".format(path))
|
|
patchinfo = PatchInfo(path + '/Cargo.toml', cratelist, crate)
|
|
if not patchinfo.get_version():
|
|
print("Couldn't extract version info from {} crate".format(crate))
|
|
exit(1)
|
|
patches += [patchinfo]
|
|
|
|
# now extract all the *other* crates
|
|
for path in cargo_toml_paths:
|
|
#print("{}".format(str(path)))
|
|
patchinfo = PatchInfo(path, cratelist)
|
|
patches += [patchinfo]
|
|
|
|
if args.bump or args.minor_bump or args.major_bump:
|
|
for (name, ver) in VERSIONS.items():
|
|
print("{}: {} -> {}".format(name, ver, bump_version(ver, which)))
|
|
if args.local_paths or args.remote_paths:
|
|
print("Target crate list")
|
|
for (name, ver) in VERSIONS.items():
|
|
print("{}: {}".format(name, ver))
|
|
|
|
if args.bump or args.minor_bump or args.major_bump:
|
|
mode = 'bump'
|
|
elif args.local_paths:
|
|
mode = 'to_local'
|
|
elif args.remote_paths:
|
|
mode = 'to_remote'
|
|
|
|
for patch in patches:
|
|
patch.debug_mode(not args.wet_run)
|
|
patch.increment_versions(mode, which)
|
|
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
print("Don't forget to check in & update git rev in Cargo.lock for: {}".format(crate_roots[1:]))
|
|
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
|
|
if args.publish:
|
|
# small quirk: if you're doing a utralib update, just use -u only.
|
|
# there is some order-sensitivity in how the dictionaries are accessed
|
|
# but of course dictionaries are unordered. I think we need to re-do
|
|
# the specifier from a dictionary to an ordered list, to guarantee that
|
|
# publishing happens in the correct operation order.
|
|
wet_cmd = ["cargo", "publish"]
|
|
dry_cmd = ["cargo", "publish", "--dry-run", "--allow-dirty"]
|
|
if args.wet_run:
|
|
cmd = wet_cmd
|
|
else:
|
|
cmd = dry_cmd
|
|
for [crate, path] in cratelist:
|
|
print("Publishing {} in {}".format(crate, path))
|
|
try:
|
|
subprocess.run(cmd, cwd=path, check=True, capture_output=True, encoding='utf-8')
|
|
except subprocess.CalledProcessError as err:
|
|
if 'already uploaded' in err.stderr:
|
|
print(" Already uploaded, skipping to next module...")
|
|
continue
|
|
|
|
print("Process failed, waiting for crates.io to update and retrying...")
|
|
time.sleep(2) # the latest Cargo seems to fix this problem
|
|
# just try running it again
|
|
try:
|
|
subprocess.run(cmd, cwd=path, check=True)
|
|
except:
|
|
print("Retry failed, moving on anyways...")
|
|
time.sleep(1)
|
|
|
|
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
print("Don't forget to manually push alternate crate roots to github: {}".format(crate_roots[1:]))
|
|
print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
exit(0)
|