add metadata parsing, basic dependencies and updates

This commit is contained in:
Lephenixnoir 2021-01-04 12:06:58 +01:00
parent 556277fc3c
commit 0b07b76bcd
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
4 changed files with 118 additions and 26 deletions

33
giteapc.py Normal file → Executable file
View file

@ -22,11 +22,8 @@ import sys
import os import os
# TODO: # TODO:
# * @ or ~ shortcut for remote repositories to avoid the -r option? # * build, install: test repository update logic
# * build, install: repository updates # * install: test dependency logic
# * NetworkError for cURL
# * Handle dependencies
# * Test update logic
usage_string = """ usage_string = """
usage: {R}giteapc{_} [{R}list{_}|{R}fetch{_}|{R}show{_}|{R}build{_}|{R}install{_}|{R}uninstall{_}] [{g}{i}ARGS...{_}] usage: {R}giteapc{_} [{R}list{_}|{R}fetch{_}|{R}show{_}|{R}build{_}|{R}install{_}|{R}uninstall{_}] [{g}{i}ARGS...{_}]
@ -37,26 +34,28 @@ is either a full name like "Lephenixnoir/sh-elf-gcc", or a short name like
"sh-elf-gcc" when there is no ambiguity. "sh-elf-gcc" when there is no ambiguity.
{R}giteapc list{_} [{R}-r{_}] [{g}{i}PATTERN{_}] {R}giteapc list{_} [{R}-r{_}] [{g}{i}PATTERN{_}]
Lists all repositories on this computer. With -r, lists repositories on the List all repositories on this computer. With -r, list repositories on the
forge. A wildcard pattern can be specified to filter the results. forge. A wildcard pattern can be specified to filter the results.
{R}giteapc fetch{_} [{R}--https{_}|{R}--ssh{_}] [{R}-u{_}] [{R}-f{_}] [{g}{i}REPOSITORY{_}[{R}@{_}{g}{i}VERSION{_}]{g}{i}...{_}] {R}giteapc fetch{_} [{R}--https{_}|{R}--ssh{_}] [{R}-u{_}] [{R}-f{_}] [{g}{i}REPOSITORY{_}[{R}@{_}{g}{i}VERSION{_}]{g}{i}...{_}]
Clones or fetches a repository and its dependencies. If no repository is Clone, fetch, or pull a repository. If no repository is specified, fetches
specified, fetches all the local repositories. HTTPS or SSH can be selected all local repositories. HTTPS or SSH can be selected when cloning (HTTPS by
when cloning (HTTPS by default). With -u, pulls after fetching. default). With -u, pulls after fetching.
{R}giteapc show{_} [{R}-r{_}] [{R}-p{_}] {g}{i}REPOSITORY...{_} {R}giteapc show{_} [{R}-r{_}] [{R}-p{_}] {g}{i}REPOSITORY...{_}
Shows the branches and tags (versions) for the specified local repositories. Show the branches and tags (versions) for the specified local repositories.
With -r, show information for remote repositories on the forge. With -r, show information for remote repositories on the forge.
With -p, just print the path of local repositories (useful in scripts). With -p, just print the path of local repositories (useful in scripts).
{R}giteapc build{_} [{R}-i{_}] [{R}-u{_}] [{R}--skip-configure{_}] {g}{i}REPOSITORY{_}[{R}@{_}{g}{i}VERSION{_}][{R}:{_}{g}{i}CONFIG{_}]{g}{i}...{_} {R}giteapc build{_} [{R}-i{_}] [{R}-u{_}] [{R}--skip-configure{_}] {g}{i}REPOSITORY{_}[{R}@{_}{g}{i}VERSION{_}][{R}:{_}{g}{i}CONFIG{_}]{g}{i}...{_}
Configures and builds a local repository. A specific configuration can be Configure and build a local repository. A specific configuration can be
requested. With -i, also installs if build is successful. --skip-configure requested. With -i, also install if build is successful. --skip-configure
builds without configuring (useful for rebuilds). With -u, pulls the current builds without configuring (useful for rebuilds). With -u, pull the current
branch before building (update mode). branch before building (update mode).
{R}giteapc install{_} [{R}--https{_}|{R}--ssh{_}] [{R}-u{_}] {g}{i}REPOSITORY{_}[{R}@{_}{g}{i}VERSION{_}][{R}:{_}{g}{i}CONFIG{_}]{g}{i}...{_} {R}giteapc install{_} [{R}--https{_}|{R}--ssh{_}] [{R}-u{_}] [{R}-y{_}] {g}{i}REPOSITORY{_}[{R}@{_}{g}{i}VERSION{_}][{R}:{_}{g}{i}CONFIG{_}]{g}{i}...{_}
Shortcut to clone (or fetch), build, and install a repository. Fetch repositories and their dependencies, then build and install them.
With -u, pulls local repositories (update mode). With -yes, do not ask for
interactive confirmation.
{R}giteapc uninstall{_} [{R}-k{_}] {g}{i}REPOSITORY...{_} {R}giteapc uninstall{_} [{R}-k{_}] {g}{i}REPOSITORY...{_}
Uninstalls the build products of the specified repositories and removes the Uninstall the build products of the specified repositories and remove the
source files. With -k, keeps the source files. source files. With -k, keep the source files.
{W}Important folders{_} {W}Important folders{_}

View file

@ -73,6 +73,7 @@ def print_repo(r, branches=None, tags=None, has_giteapc=True):
if r.remote: if r.remote:
print(("\n" + r.description).replace("\n", "\n ")[1:]) print(("\n" + r.description).replace("\n", "\n ")[1:])
else: else:
print(" {W}HEAD:{_}".format(**colors()), r.describe(all=True))
print(" {W}Path:{_}".format(**colors()), r.folder, end="") print(" {W}Path:{_}".format(**colors()), r.folder, end="")
if os.path.islink(r.folder): if os.path.islink(r.folder):
print(" ->", os.readlink(r.folder)) print(" ->", os.readlink(r.folder))
@ -108,6 +109,11 @@ def split_config(name):
repo, version, config = m[1], m[2] or "", m[3] or "" repo, version, config = m[1], m[2] or "", m[3] or ""
return repo, version, config return repo, version, config
def make_config(name, version, config):
version = f"@{version}" if version else ""
config = f":{config}" if config else ""
return name + version + config
# #
# repo list command # repo list command
# #
@ -238,8 +244,6 @@ def build(*args, install=False, skip_configure=False, update=False):
repo = resolve(repo, local_only=True) repo = resolve(repo, local_only=True)
specs.append((repo, version, config)) specs.append((repo, version, config))
msg("Will build:", ", ".join(pretty_repo(spec[0]) for spec in specs))
for (r, version, config) in specs: for (r, version, config) in specs:
pretty = pretty_repo(r) pretty = pretty_repo(r)
config_string = f" for {config}" if config else "" config_string = f" for {config}" if config else ""
@ -247,8 +251,14 @@ def build(*args, install=False, skip_configure=False, update=False):
if version != "": if version != "":
msg("{}: Checking out {W}{}{_}".format(pretty, version, **colors())) msg("{}: Checking out {W}{}{_}".format(pretty, version, **colors()))
r.checkout(version) r.checkout(version)
if update: if update:
r.pull() previous = r.describe()
r.pull()
new = r.describe()
if new == previous:
msg("{}: Still at {W}{}{_}, skipping build".format(pretty,
previous, **colors()))
continue
# Check that the project has a Makefile # Check that the project has a Makefile
if not os.path.exists(r.makefile): if not os.path.exists(r.makefile):
@ -277,13 +287,59 @@ def build(*args, install=False, skip_configure=False, update=False):
# repo install command # repo install command
# #
def install(*args, use_https=False, use_ssh=False, update=False): def install(*args, use_https=False, use_ssh=False, update=False, yes=False):
if args == (): if args == ():
return 0 return 0
def recursive_fetch(spec, fetched_so_far):
repos_to_build = []
repo, version, config = split_config(spec)
r = resolve(repo, local_only=True)
if r.fullname not in fetched_so_far:
fetched_so_far.add(r.fullname)
fetch(repo, use_https=use_https, use_ssh=use_ssh, update=update)
for dep_spec in r.dependencies():
rtb, fsf = recursive_fetch(dep_spec, fetched_so_far)
repos_to_build += rtb
fetched_so_far = fetched_so_far.union(fsf)
return repos_to_build + [(r, version, config)], fetched_so_far
# First download every repository, and only then build # First download every repository, and only then build
fetch(*args, use_https=use_https, use_ssh=use_ssh) repos_to_build = []
build(*args, install=True, update=update) fetched_so_far = set()
for spec in args:
rtb, fsf = recursive_fetch(spec, fetched_so_far)
repos_to_build += rtb
fetched_so_far = fetched_so_far.union(fsf)
# Eliminate duplicates and look for version collisions
rd = dict()
for (r, version, config) in repos_to_build:
name = r.fullname
if name not in rd:
rd[name] = (version, config)
elif rd[name] != (version, config):
s1 = make_config(name, *rd[name])
s2 = make_config(name, version, config)
return fatal(f"repo install: cannot install both {s1} and {s2}")
msg("Will install:", ", ".join(make_config(pretty_repo(r), version, config)
for (r, version, config) in repos_to_build))
if not yes:
msg("Is that okay (Y/n)? ", end="")
confirm = input().strip()
if confirm not in ["Y", "y", ""]:
return 1
for (r, version, config) in repos_to_build:
build(make_config(r.fullname, version, config), install=True,
update=update)
# #
# repo uninstall command # repo uninstall command

View file

@ -5,6 +5,7 @@ from subprocess import PIPE
import os.path import os.path
import shutil import shutil
import glob import glob
import re
class RemoteRepo: class RemoteRepo:
# Create a remote repo from the JSON object returned by Gitea # Create a remote repo from the JSON object returned by Gitea
@ -93,7 +94,7 @@ class LocalRepo:
def is_on_branch(self): def is_on_branch(self):
try: try:
self._git(["symbolic-ref", "-q", "HEAD"]) self._git(["symbolic-ref", "-q", "HEAD"], stdout=PIPE)
return True return True
except ProcessError as e: except ProcessError as e:
if e.returncode == 1: if e.returncode == 1:
@ -107,6 +108,11 @@ class LocalRepo:
if self.is_on_branch(): if self.is_on_branch():
self._git(["pull"]) self._git(["pull"])
def describe(self, tags=True, all=False, always=True):
args = ["--tags"]*tags + ["--all"]*all + ["--always"]*always
p = self._git(["describe"] + args, stdout=PIPE)
return p.stdout.decode("utf-8").strip()
def checkout(self, version): def checkout(self, version):
self._git(["checkout", version], stdout=PIPE, stderr=PIPE) self._git(["checkout", version], stdout=PIPE, stderr=PIPE)
@ -134,3 +140,33 @@ class LocalRepo:
def make(self, target, env=None): def make(self, target, env=None):
with ChangeDirectory(self.folder): with ChangeDirectory(self.folder):
return run(["make", "-f", "giteapc.make", target], env=env) return run(["make", "-f", "giteapc.make", target], env=env)
# Metadata
def metadata(self):
RE_METADATA = re.compile(r'^\s*#\s*giteapc\s*:\s*(.*)$')
metadata = dict()
with open(self.folder + "/giteapc.make", "r") as fp:
makefile = fp.read()
for line in makefile.split("\n"):
m = re.match(RE_METADATA, line)
if m is None:
break
for assignment in m[1].split():
name, value = assignment.split("=", 1)
if name not in metadata:
metadata[name] = value
else:
metadata[name] += "," + value
return metadata
def dependencies(self):
metadata = self.metadata()
if "depends" in metadata:
return metadata["depends"].split(",")
else:
return []

View file

@ -133,7 +133,8 @@ def curl_get(url, params):
if params: if params:
url = url + "?" + "&".join(f"{n}={v}" for (n,v) in params.items()) url = url + "?" + "&".join(f"{n}={v}" for (n,v) in params.items())
proc = subprocess.run(["curl", "-s", url], stdout=subprocess.PIPE) proc = subprocess.run(["curl", "-s", url], stdout=subprocess.PIPE)
assert proc.returncode == 0 if proc.returncode != 0:
raise NetworkError(-1)
return proc.stdout.decode("utf-8") return proc.stdout.decode("utf-8")
# Create path to folder # Create path to folder