mach-nix/mach_nix/run.py

232 lines
7.5 KiB
Python
Raw Normal View History

2020-04-22 09:28:58 +00:00
import json
import os
import subprocess as sp
import sys
import tempfile
from argparse import ArgumentParser
from os.path import realpath, dirname, isfile
from textwrap import dedent
from urllib import request
from urllib.error import HTTPError
2020-04-22 09:28:58 +00:00
import toml
from mach_nix.ensure_nix import ensure_nix
2020-04-22 09:28:58 +00:00
from mach_nix.versions import PyVer
pwd = dirname(realpath(__file__))
def gen(args, nixpkgs_rev, nixpkgs_sha256, return_expr=False):
2020-04-22 09:28:58 +00:00
with open(args.r) as f:
requirements = f.read().strip()
o_file = tempfile.mktemp()
py_ver = PyVer(args.python)
cmd = f'nix-build {pwd}/nix/call_mach.nix -o {o_file}' \
2020-04-22 09:28:58 +00:00
f' --argstr requirements "{requirements}"' \
f' --argstr python_attr python{py_ver.digits()}' \
f' --argstr nixpkgs_rev {nixpkgs_rev}' \
f' --argstr nixpkgs_sha {nixpkgs_sha256}'
proc = sp.run(cmd, shell=True, stdout=sys.stderr)
2020-04-22 09:28:58 +00:00
if proc.returncode:
exit(1)
with open(f"{o_file}/share/mach_nix_file.nix") as src:
2020-04-22 09:28:58 +00:00
expr = src.read()
if return_expr:
return expr
if getattr(args, 'o', None):
with open(args.o, 'w') as dest:
dest.write(expr)
print(f"Expression written to {args.o}")
else:
print(expr)
def env(args, nixpkgs_ref):
2020-04-22 09:28:58 +00:00
target_dir = args.directory
py_ver = PyVer(args.python)
2020-04-22 09:28:58 +00:00
default_nix_file = f"{target_dir}/default.nix"
inputs_nix_file = f"{target_dir}/inputs.nix"
lock_file = f"{target_dir}/lock.toml"
python_nix_file = f"{target_dir}/python.nix"
requirements_file = f"{target_dir}/requirements.txt"
shell_nix_file = f"{target_dir}/shell.nix"
2020-11-27 06:51:44 +00:00
machnix_version = os.environ.get("MACHNIX_VERSION", default=None)
if machnix_version is None:
with open(f"{pwd}/VERSION") as f:
machnix_version = f.read()
with open(args.r) as f:
requirements = f.read().strip()
2020-11-27 06:51:44 +00:00
inputs_nix_content = dedent(f"""
with builtins;
let
lock = fromTOML (readFile ./lock.toml);
in rec {{
pkgs = import (builtins.fetchTarball {{
name = "nixpkgs";
url = "https://github.com/nixos/nixpkgs/tarball/${{lock.nixpkgs.rev}}";
sha256 = "${{lock.nixpkgs.sha256}}";
}}) {{ config = {{}}; overlays = []; }};
mach-nix = import (builtins.fetchTarball {{
url = "https://github.com/DavHau/mach-nix/tarball/${{lock.mach-nix.rev}}";
sha256 = lock.mach-nix.sha256;
}}) {{
python = "python{py_ver.digits()}";
inherit pkgs;
}};
}}
""")
python_nix_content = dedent(f"""
with (import ./inputs.nix);
mach-nix.mkPython {{
requirements = builtins.readFile ./requirements.txt;
}}
""")
shell_nix_content = dedent(f"""
with (import ./inputs.nix);
pkgs.mkShell {{
buildInputs = [
mach-nix.mach-nix
(import ./python.nix)
];
}}
""")
# ensure target path exists
2020-04-22 09:28:58 +00:00
if not os.path.isdir(target_dir):
if os.path.exists(target_dir):
print(f'Error: {target_dir} already exists and is not a directory!')
exit(1)
os.mkdir(target_dir)
# update lock file if mach-nix version mismatch
update_lock_file(lock_file, 'DavHau', "mach-nix", machnix_version)
update_lock_file(lock_file, 'nixos', "nixpkgs", nixpkgs_ref)
# write requirements file
with open(default_nix_file, 'w') as default:
default.write("import ./shell.nix\n")
with open(inputs_nix_file, 'w') as default:
default.write(inputs_nix_content)
2020-04-22 09:28:58 +00:00
with open(python_nix_file, 'w') as python:
python.write(python_nix_content)
with open(requirements_file, "w") as dest:
dest.write(requirements)
with open(shell_nix_file, 'w') as shell:
shell.write(shell_nix_content)
class c:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
print(f"\nInitialized python environment in: {c.OKCYAN}{target_dir}{c.ENDC}\n"
f"To change python requirements, modify the file: {c.OKCYAN}{requirements_file}{c.ENDC}\n\n"
f"To activate the environment, execute: {c.BOLD}{c.OKGREEN}nix-shell {target_dir}{c.ENDC}")
def update_lock_file(file, owner, project, ref):
lock = {}
lock_ok = False
if isfile(file):
with open(file) as f:
lock = toml.load(f)
if project in lock\
and all(k in lock[project] for k in ("ref", "rev", "sha256")) \
and lock[project]['ref'].split('/')[-1] == ref:
lock_ok = True
if not lock_ok:
rev, sha256 = github_rev_and_sha256(owner, project, ref)
lock[project] = dict(
ref=ref,
rev=rev,
sha256=sha256)
with open(file, 'w') as f:
toml.dump(lock, f)
2020-04-22 09:28:58 +00:00
def github_rev_and_sha256(owner, repo, ref):
try:
res = request.urlopen(f"https://api.github.com/repos/{owner}/{repo}/commits/{ref}").read()
except HTTPError as e:
2020-11-27 06:51:44 +00:00
print(f"Error receiving {repo} revision for {ref}: {e.msg}")
exit(1)
commit = json.loads(res)['sha']
proc = sp.run(
f"nix-prefetch-url --unpack https://github.com/{owner}/{repo}/tarball/{commit}",
shell=True, check=True, stdout=sp.PIPE)
sha256 = proc.stdout.decode().strip()
return commit, sha256
def parse_args(parser: ArgumentParser, nixpkgs_ref):
2020-04-23 04:26:23 +00:00
common_arguments = (
(('-p', '--python'), dict(
help='select python version (default: 3.7)',
choices=('2.7', '3.5', '3.6', '3.7', '3.8'),
default='3.7')),
(('-r',), dict(
help='path to requirements.txt file',
metavar='requirements.txt',
required=True)),
(('--nixpkgs',), dict(
2020-11-27 06:51:44 +00:00
help=dedent('select nixpkgs revision. Can be a branch or tag or revision'),
default=nixpkgs_ref,
required=False, )),
2020-04-23 04:26:23 +00:00
)
parser.add_argument('--version', '-V', help='show program version', action='store_true')
subparsers = parser.add_subparsers(dest='command')
2020-04-22 09:28:58 +00:00
gen_parser = subparsers.add_parser('gen', help='generate a nix expression')
2020-04-23 04:26:23 +00:00
gen_parser.add_argument('-o', help='output file. defaults to stdout', metavar='python.nix')
2020-04-22 09:28:58 +00:00
env_parser = subparsers.add_parser('env', help='set up a venv-style environment')
env_parser.add_argument('directory', help='target directory to create the environment')
2020-04-23 04:26:23 +00:00
for p in (gen_parser, env_parser):
for args, kwargs in common_arguments:
p.add_argument(*args, **kwargs)
return parser.parse_args()
def main():
# read nixpkgs json file for revision ref
flakes_lock = f"""{pwd}/flake.lock"""
with open(flakes_lock, 'r') as f:
nixpkgs_ref = json.load(f)['nodes']['nixpkgs']['locked']['rev']
parser = ArgumentParser()
args = parse_args(parser, nixpkgs_ref)
2020-04-22 09:28:58 +00:00
if args.version:
with open(f"{pwd}/VERSION") as f:
print(f.read())
exit(0)
if args.command not in ('gen', 'env'):
parser.print_usage()
exit(1)
2020-04-22 09:28:58 +00:00
ensure_nix()
2020-04-22 09:28:58 +00:00
if args.command == 'gen':
gen(args, *github_rev_and_sha256('nixos', 'nixpkgs', args.nixpkgs))
2020-04-22 09:28:58 +00:00
elif args.command == 'env':
env(args, nixpkgs_ref=args.nixpkgs)
2020-04-22 09:28:58 +00:00
if __name__ == "__main__":
main()