|
1 | | -from . import CONFIG |
| 1 | +import os |
| 2 | +import semantic_version as semver |
| 3 | +import sys |
| 4 | + |
| 5 | +from collections import namedtuple |
| 6 | +from time import time |
| 7 | + |
| 8 | +from . import CONFIG, __version__ |
| 9 | + |
| 10 | +### META |
2 | 11 |
|
3 | 12 | def load_meta(): |
4 | 13 | import json, os.path |
@@ -35,33 +44,134 @@ def set_meta(*args): |
35 | 44 | meta2[keys[-1]] = value |
36 | 45 | save_meta(meta) |
37 | 46 |
|
38 | | -def get_dep(package, name): |
39 | | - return get_meta('pydeps', package, name) |
| 47 | +### VERSION PARSING |
40 | 48 |
|
41 | | -def set_dep(package, name, value): |
42 | | - set_meta('pydeps', package, name, value) |
| 49 | +@semver.base.BaseSpec.register_syntax |
| 50 | +class JuliaVersionSpec(semver.SimpleSpec): |
| 51 | + SYNTAX = 'julia' |
| 52 | + class Parser(semver.SimpleSpec.Parser): |
| 53 | + PREFIX_ALIASES = {'=': '==', '': '^'} |
| 54 | + @classmethod |
| 55 | + def parse(cls, expression): |
| 56 | + blocks = expression.split(',') |
| 57 | + clause = semver.base.Never() |
| 58 | + for block in blocks: |
| 59 | + block = block.strip() |
| 60 | + if not cls.NAIVE_SPEC.match(block): |
| 61 | + raise ValueError('Invalid simple block %r' % block) |
| 62 | + clause |= cls.parse_block(block) |
| 63 | + return clause |
43 | 64 |
|
44 | | -def require(package, name, value, func=None): |
45 | | - if func is None: |
46 | | - return Require(package, name, value) |
47 | | - elif get_dep(package, name) != value: |
48 | | - func() |
49 | | - set_dep(package, name, value) |
| 65 | +### RESOLVE |
50 | 66 |
|
51 | | -class Require: |
52 | | - def __init__(self, package, name, value): |
53 | | - self.package = package |
| 67 | +class PackageSpec: |
| 68 | + def __init__(self, name, uuid, dev=False, compat=None, path=None, url=None, rev=None, version=None): |
54 | 69 | self.name = name |
55 | | - self.value = value |
56 | | - def __enter__(self): |
57 | | - self.required = get_dep(self.package, self.name) != self.value |
58 | | - return self.required |
59 | | - def __exit__(self, t, v, b): |
60 | | - if t is None and self.required: |
61 | | - set_dep(self.package, self.name, self.value) |
62 | | - |
63 | | -def require_julia(package, name, version): |
64 | | - with require(package, name, version) as required: |
65 | | - if required: |
66 | | - from juliacall import Pkg |
67 | | - Pkg.add(name=name, version=version) |
| 70 | + self.uuid = uuid |
| 71 | + self.dev = dev |
| 72 | + self.compat = compat |
| 73 | + self.path = path |
| 74 | + self.url = url |
| 75 | + self.rev = rev |
| 76 | + self.version = version |
| 77 | + |
| 78 | + def jlstr(self): |
| 79 | + args = ['name="{}"'.format(self.name), 'uuid="{}"'.format(self.uuid)] |
| 80 | + if self.path is not None: |
| 81 | + args.append('path=raw"{}"'.format(self.path)) |
| 82 | + if self.url is not None: |
| 83 | + args.append('url=raw"{}"'.format(self.url)) |
| 84 | + if self.rev is not None: |
| 85 | + args.append('rev=raw"{}"'.format(self.rev)) |
| 86 | + if self.version is not None: |
| 87 | + args.append('version=raw"{}"'.format(self.version)) |
| 88 | + return "Pkg.PackageSpec({})".format(', '.join(args)) |
| 89 | + |
| 90 | + def dict(self): |
| 91 | + ans = { |
| 92 | + "name": self.name, |
| 93 | + "uuid": self.uuid, |
| 94 | + "dev": self.dev, |
| 95 | + "compat": self.compat, |
| 96 | + "path": self.path, |
| 97 | + "url": self.url, |
| 98 | + "rev": self.rev, |
| 99 | + "version": self.version, |
| 100 | + } |
| 101 | + return {k:v for (k,v) in ans.items() if v is not None} |
| 102 | + |
| 103 | +def can_skip_resolve(): |
| 104 | + # resolve if we haven't resolved before |
| 105 | + deps = get_meta("pydeps") |
| 106 | + if deps is None: |
| 107 | + return False |
| 108 | + # resolve whenever the version changes |
| 109 | + version = deps.get("version") |
| 110 | + if version is None or version != __version__: |
| 111 | + return False |
| 112 | + # resolve whenever swapping between dev/not dev |
| 113 | + isdev = deps.get("dev") |
| 114 | + if isdev is None or isdev != CONFIG["dev"]: |
| 115 | + return False |
| 116 | + # resolve whenever anything in sys.path changes |
| 117 | + timestamp = deps.get("timestamp") |
| 118 | + if timestamp is None: |
| 119 | + return False |
| 120 | + sys_path = deps.get("sys_path") |
| 121 | + if sys_path is None or sys_path != sys.path: |
| 122 | + return False |
| 123 | + for path in sys.path: |
| 124 | + if not path: |
| 125 | + path = os.getcwd() |
| 126 | + if not os.path.exists(path): |
| 127 | + continue |
| 128 | + if os.path.getmtime(path) > timestamp: |
| 129 | + return False |
| 130 | + if os.path.isdir(path): |
| 131 | + fn = os.path.join(path, "juliacalldeps.json") |
| 132 | + if os.path.exists(fn) and os.path.getmtime(fn) > timestamp: |
| 133 | + return False |
| 134 | + return True |
| 135 | + |
| 136 | +def deps_files(): |
| 137 | + ans = [] |
| 138 | + for path in sys.path: |
| 139 | + if not path: |
| 140 | + path = os.getcwd() |
| 141 | + if not os.path.isdir(path): |
| 142 | + continue |
| 143 | + fn = os.path.join(path, "juliacalldeps.json") |
| 144 | + if os.path.isfile(fn): |
| 145 | + ans.append(fn) |
| 146 | + for subdir in os.listdir(path): |
| 147 | + fn = os.path.join(path, subdir, "juliacalldeps.json") |
| 148 | + if os.path.isfile(fn): |
| 149 | + ans.append(fn) |
| 150 | + return list(set(ans)) |
| 151 | + |
| 152 | +def required_packages(): |
| 153 | + import json |
| 154 | + ans = {} |
| 155 | + for fn in deps_files(): |
| 156 | + with open(fn) as fp: |
| 157 | + deps = json.load(fp) |
| 158 | + for (name, kw) in deps.get("packages", {}).items(): |
| 159 | + if name in ans: |
| 160 | + p = ans[name] |
| 161 | + if p.uuid != kw["uuid"]: |
| 162 | + raise Exception("found multiple UUIDs for package '{}'".format(name)) |
| 163 | + # todo: dev, compat, path, url, rev, version |
| 164 | + raise NotImplementedError("need to merge repeated dependency '{}'") |
| 165 | + else: |
| 166 | + p = PackageSpec(name=name, **kw) |
| 167 | + ans[p.name] = p |
| 168 | + return list(ans.values()) |
| 169 | + |
| 170 | +def record_resolve(pkgs): |
| 171 | + set_meta("pydeps", { |
| 172 | + "version": __version__, |
| 173 | + "dev": CONFIG["dev"], |
| 174 | + "timestamp": time(), |
| 175 | + "sys_path": sys.path, |
| 176 | + "pkgs": [pkg.dict() for pkg in pkgs], |
| 177 | + }) |
0 commit comments