mirror of
https://git.planet-casio.com/Lephenixnoir/GiteaPC.git
synced 2025-06-02 08:55:10 +02:00
add metadata parsing, basic dependencies and updates
This commit is contained in:
parent
556277fc3c
commit
0b07b76bcd
4 changed files with 118 additions and 26 deletions
33
giteapc.py
Normal file → Executable file
33
giteapc.py
Normal file → Executable 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{_}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 []
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue