Compare commits

...

10 commits

4 changed files with 62 additions and 43 deletions

View file

@ -1,4 +1,4 @@
__version__ = "0.2.5" __version__ = "0.2.5-5"
__author__ = "Reid D. 'arrdem' McKenzie <me@arrdem.com>" __author__ = "Reid D. 'arrdem' McKenzie <me@arrdem.com>"
__copyright__ = "Copyright 2020" __copyright__ = "Copyright 2020"
__license__ = "https://anticapitalist.software/" __license__ = "https://anticapitalist.software/"

View file

@ -3,8 +3,6 @@
import logging import logging
import os import os
import pickle import pickle
import platform
import re
from collections import defaultdict from collections import defaultdict
from pathlib import Path from pathlib import Path
from pprint import pformat from pprint import pformat
@ -18,6 +16,7 @@ from vfs import Vfs
from . import __author__, __copyright__, __license__, __version__ from . import __author__, __copyright__, __license__, __version__
from .v0 import PackageV0, ProfileV0 from .v0 import PackageV0, ProfileV0
from .v1 import LightPackageV1, PackageV1, ProfileV1 from .v1 import LightPackageV1, PackageV1, ProfileV1
from .common import expandvars
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -60,29 +59,6 @@ def upsearch(name: Union[str, Path], limit=10) -> Optional[Path]:
limit -= 1 limit -= 1
def expandvars(s: str) -> str:
def _repl(m: re.Match) -> str:
var = m.group(0).lower()
match var:
case "${hostname}":
return os.uname()[1].split(".")[0]
case "${fqdn}":
return os.uname()[1]
case "${home}":
return os.path.expanduser("~")
case "${uname}" | "${sysname}":
return platform.system().lower()
case "${arch}":
return platform.machine()
case "${release}":
return platform.release()
case _:
return os.path.expandvars(m.group(0))
s = re.sub(r"\${?([^\s}]+)}?", "${\\1}", s)
return re.sub(r"\$\{.*?\}", _repl, s)
def handle_vars(_ctx, _param, s): def handle_vars(_ctx, _param, s):
return expandvars(s) return expandvars(s)
@ -218,6 +194,10 @@ def load_state(statefile: Path) -> Vfs:
return oldfs return oldfs
def remove_all(l, it):
return [i for i in l if i != it]
def simplify(old_fs: Vfs, new_fs: Vfs, /, exec_idempotent=True) -> Vfs: def simplify(old_fs: Vfs, new_fs: Vfs, /, exec_idempotent=True) -> Vfs:
"""Try to reduce a new VFS using diff from the original VFS.""" """Try to reduce a new VFS using diff from the original VFS."""
@ -226,15 +206,12 @@ def simplify(old_fs: Vfs, new_fs: Vfs, /, exec_idempotent=True) -> Vfs:
initial_new_steps = len(new_fs._log) initial_new_steps = len(new_fs._log)
# Scrub anything in the new log that's in the old log # Scrub anything in the new log that's in the old log
for txn in list(old_fs._log): for txn in old_fs._log:
# Except for execs which are stateful # Except for execs which are stateful
if txn[0] == "exec" and not exec_idempotent: if txn[0] == "exec" and not exec_idempotent:
continue continue
try: new_fs._log = remove_all(new_fs._log, txn)
new_fs._log.remove(txn)
except ValueError:
pass
# Dedupe the new log while preserving order. # Dedupe the new log while preserving order.
keys = set() keys = set()
@ -244,6 +221,7 @@ def simplify(old_fs: Vfs, new_fs: Vfs, /, exec_idempotent=True) -> Vfs:
if key not in keys: if key not in keys:
keys.add(key) keys.add(key)
deduped.append(op) deduped.append(op)
new_fs._log = deduped new_fs._log = deduped
log.info(f"Optimized out {initial_new_steps - len(new_fs._log)} steps") log.info(f"Optimized out {initial_new_steps - len(new_fs._log)} steps")
@ -278,6 +256,8 @@ def configure(ctx, param, filename: Optional[Path]):
log.debug(f"Loading config from {filename}") log.debug(f"Loading config from {filename}")
ctx.obj = cfg = toml.load(filename) ctx.obj = cfg = toml.load(filename)
assert cfg["cram"]["version"] >= 1 assert cfg["cram"]["version"] >= 1
cfg["cram"]["config"] = str(filename.absolute())
cfg["cram"]["root"] = str(filename.absolute().parent)
task_cfg = cfg.get("cram", {}).get("task", {}) task_cfg = cfg.get("cram", {}).get("task", {})
defaults = task_cfg.get("default", {}) defaults = task_cfg.get("default", {})
@ -599,6 +579,7 @@ version = 1
confdir = "${PWD}" confdir = "${PWD}"
destdir = "${HOME}" destdir = "${HOME}"
state_file = "${PWD}/.cram.log" state_file = "${PWD}/.cram.log"
cache_dir = "${HOME}/.cache/cram"
optimize = true optimize = true
exec_idempotent = true exec_idempotent = true
# Where to load requirements from, and the requirement type # Where to load requirements from, and the requirement type

View file

@ -5,6 +5,8 @@ import sys
from pathlib import Path from pathlib import Path
from shlex import quote as sh_quote from shlex import quote as sh_quote
from typing import List, Optional from typing import List, Optional
import re
import platform
from vfs import Vfs from vfs import Vfs
@ -47,8 +49,8 @@ def stow(fs: Vfs, src_dir: Path, dest_dir: Path, skip=[]):
dest = dest_root / src.relative_to(src_root) dest = dest_root / src.relative_to(src_root)
if src.is_symlink(): if src.is_symlink():
fs.link(src.readlink().resolve(), dest) fs.link(src.readlink().resolve(), dest)
elif src.is_dir(): elif src.is_dir():
fs.mkdir(dest) fs.mkdir(dest)
fs.chmod(dest, src.stat().st_mode) fs.chmod(dest, src.stat().st_mode)
@ -86,3 +88,26 @@ class Package(object):
def post_install(self, fs: Vfs, dest: Path): def post_install(self, fs: Vfs, dest: Path):
pass pass
def expandvars(s: str) -> str:
def _repl(m: re.Match) -> str:
var = m.group(0).lower()
match var:
case "${hostname}":
return os.uname()[1].split(".")[0]
case "${fqdn}":
return os.uname()[1]
case "${home}":
return os.path.expanduser("~")
case "${uname}" | "${sysname}":
return platform.system().lower()
case "${arch}":
return platform.machine()
case "${release}":
return platform.release()
case _:
return os.path.expandvars(m.group(0))
s = re.sub(r"\${?([^\s}]+)}?", "${\\1}", s)
return re.sub(r"\$\{.*?\}", _repl, s)

View file

@ -12,13 +12,17 @@ from typing import List, Optional, Union
import toml import toml
from vfs import Vfs from vfs import Vfs
from .common import Package, sh, stow from .common import Package, sh, stow, expandvars
def tempf(name): def tempf(global_config, name):
root = Path("/tmp/stow") root = Path(global_config["cram"]["root"])
root.mkdir(exist_ok=True, parents=True) assert root.exists() and root.is_dir()
return root / name
cache_dir = global_config.get("cram", {}).get("task", {}).get("default", {}).get("cache_dir")
cache_root = cache_dir and Path(expandvars(cache_dir)) or root / ".cache"
return cache_root / name[0:2] / name
class PackageV1(Package): class PackageV1(Package):
@ -71,15 +75,24 @@ class PackageV1(Package):
sum = sum.hexdigest() sum = sum.hexdigest()
installf = self.root / content installf = self.root / content
if installf.exists():
def _installf_exists():
try:
return installf.exists()
except OSError as e:
return False
if content.startswith("#!") or "\n" in content or not _installf_exists():
f = tempf(self.global_config, f"{sum}.sh")
f.parent.mkdir(exist_ok=True, parents=True)
with open(f, "w") as fp:
fp.write(content)
fs.exec(cwd, sh(self.global_config, [str(f)]))
else:
with open(installf, "r") as fp: with open(installf, "r") as fp:
self.do_sh_or_script(fp.read(), fs, dest) self.do_sh_or_script(fp.read(), fs, dest)
elif content:
f = tempf(f"{sum}.sh")
with open(f, "w") as fp:
fp.write(content)
fs.exec(cwd, sh(self.global_config, [f]))
def do_build(self, fs: Vfs, dest: Path): def do_build(self, fs: Vfs, dest: Path):
self.do_sh_or_script(self.config().get("package", {}).get("build"), fs, dest) self.do_sh_or_script(self.config().get("package", {}).get("build"), fs, dest)