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
Signed by untrusted user: Lephenixnoir
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
# TODO:
# * @ or ~ shortcut for remote repositories to avoid the -r option?
# * build, install: repository updates
# * NetworkError for cURL
# * Handle dependencies
# * Test update logic
# * build, install: test repository update logic
# * install: test dependency logic
usage_string = """
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.
{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.
{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
specified, fetches all the local repositories. HTTPS or SSH can be selected
when cloning (HTTPS by default). With -u, pulls after fetching.
Clone, fetch, or pull a repository. If no repository is specified, fetches
all local repositories. HTTPS or SSH can be selected when cloning (HTTPS by
default). With -u, pulls after fetching.
{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 -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}...{_}
Configures and builds a local repository. A specific configuration can be
requested. With -i, also installs if build is successful. --skip-configure
builds without configuring (useful for rebuilds). With -u, pulls the current
Configure and build a local repository. A specific configuration can be
requested. With -i, also install if build is successful. --skip-configure
builds without configuring (useful for rebuilds). With -u, pull the current
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}...{_}
Shortcut to clone (or fetch), build, and install a repository.
{R}giteapc install{_} [{R}--https{_}|{R}--ssh{_}] [{R}-u{_}] [{R}-y{_}] {g}{i}REPOSITORY{_}[{R}@{_}{g}{i}VERSION{_}][{R}:{_}{g}{i}CONFIG{_}]{g}{i}...{_}
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...{_}
Uninstalls the build products of the specified repositories and removes the
source files. With -k, keeps the source files.
Uninstall the build products of the specified repositories and remove the
source files. With -k, keep the source files.
{W}Important folders{_}

View File

@ -73,6 +73,7 @@ def print_repo(r, branches=None, tags=None, has_giteapc=True):
if r.remote:
print(("\n" + r.description).replace("\n", "\n ")[1:])
else:
print(" {W}HEAD:{_}".format(**colors()), r.describe(all=True))
print(" {W}Path:{_}".format(**colors()), r.folder, end="")
if os.path.islink(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 ""
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
#
@ -238,8 +244,6 @@ def build(*args, install=False, skip_configure=False, update=False):
repo = resolve(repo, local_only=True)
specs.append((repo, version, config))
msg("Will build:", ", ".join(pretty_repo(spec[0]) for spec in specs))
for (r, version, config) in specs:
pretty = pretty_repo(r)
config_string = f" for {config}" if config else ""
@ -247,8 +251,14 @@ def build(*args, install=False, skip_configure=False, update=False):
if version != "":
msg("{}: Checking out {W}{}{_}".format(pretty, version, **colors()))
r.checkout(version)
if update:
r.pull()
if update:
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
if not os.path.exists(r.makefile):
@ -277,13 +287,59 @@ def build(*args, install=False, skip_configure=False, update=False):
# 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 == ():
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
fetch(*args, use_https=use_https, use_ssh=use_ssh)
build(*args, install=True, update=update)
repos_to_build = []
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

View File

@ -5,6 +5,7 @@ from subprocess import PIPE
import os.path
import shutil
import glob
import re
class RemoteRepo:
# Create a remote repo from the JSON object returned by Gitea
@ -93,7 +94,7 @@ class LocalRepo:
def is_on_branch(self):
try:
self._git(["symbolic-ref", "-q", "HEAD"])
self._git(["symbolic-ref", "-q", "HEAD"], stdout=PIPE)
return True
except ProcessError as e:
if e.returncode == 1:
@ -107,6 +108,11 @@ class LocalRepo:
if self.is_on_branch():
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):
self._git(["checkout", version], stdout=PIPE, stderr=PIPE)
@ -134,3 +140,33 @@ class LocalRepo:
def make(self, target, env=None):
with ChangeDirectory(self.folder):
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:
url = url + "?" + "&".join(f"{n}={v}" for (n,v) in params.items())
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")
# Create path to folder