Compare commits
10 commits
73c9e68423
...
0562772dc8
Author | SHA1 | Date | |
---|---|---|---|
0562772dc8 | |||
6480a51a82 | |||
712731f4fe | |||
f4308ac0c7 | |||
5da29e1d9d | |||
22a12ae817 | |||
ca333bf74c | |||
4591c62baa | |||
6b31ccfa6e | |||
f7bfe0eac3 |
4 changed files with 62 additions and 43 deletions
|
@ -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/"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue