forked from Lephenixnoir/GiteaPC
add metadata parsing, basic dependencies and updates
This commit is contained in:
parent
556277fc3c
commit
0b07b76bcd
|
@ -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{_}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 []
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue