Skip to content
This repository was archived by the owner on Apr 25, 2024. It is now read-only.

Commit c5bf0c2

Browse files
authored
Merge branch 'master' into noah/subproof-split
2 parents 0d95e58 + abed829 commit c5bf0c2

File tree

5 files changed

+141
-77
lines changed

5 files changed

+141
-77
lines changed

package/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.446
1+
0.1.447

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "pyk"
7-
version = "0.1.446"
7+
version = "0.1.447"
88
description = ""
99
authors = [
1010
"Runtime Verification, Inc. <[email protected]>",

src/pyk/kbuild/kbuild.py

Lines changed: 78 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
from __future__ import annotations
22

33
import re
4+
from contextlib import contextmanager
45
from dataclasses import dataclass
56
from functools import cached_property
67
from pathlib import Path
8+
from tempfile import TemporaryDirectory
79
from typing import TYPE_CHECKING, final
810

911
from filelock import FileLock
1012

1113
from ..ktool.kompile import kompile
12-
from ..utils import single
14+
from ..utils import check_dir_path, single
1315
from .utils import k_version, sync_files
1416

1517
if TYPE_CHECKING:
18+
from collections.abc import Iterator
1619
from re import Match
1720
from typing import Any
1821

@@ -33,65 +36,19 @@ def k_version(self) -> str:
3336
return k_version().text
3437

3538
def definition_dir(self, project: Project, target_name: str) -> Path:
36-
return self.kdist_dir / project.name / 'target' / self.k_version / target_name
37-
38-
def resource_dir(self, project: Project, resource_name: str) -> Path:
39-
return self.kdist_dir / project.name / 'resource' / resource_name
40-
41-
def resource_files(self, project: Project, resource_name: str) -> list[Path]:
42-
return [
43-
self.resource_dir(project, resource_name) / file_name
44-
for file_name in project.resource_file_names[resource_name]
45-
]
46-
47-
def include_dir(self, project: Project) -> Path:
48-
return self.kdist_dir / project.name / 'include'
49-
50-
def source_dir(self, project: Project) -> Path:
51-
return self.include_dir(project) / project.name
52-
53-
def source_files(self, project: Project) -> list[Path]:
54-
return [self.source_dir(project) / file_name for file_name in project.source_file_names]
55-
56-
def sync(self, project: Project) -> list[Path]:
57-
res: list[Path] = []
58-
59-
# Sync sources
60-
res += sync_files(
61-
source_dir=project.source_dir,
62-
target_dir=self.source_dir(project),
63-
file_names=project.source_file_names,
64-
)
65-
66-
# Sync resources
67-
for resource_name in project.resources:
68-
res += sync_files(
69-
source_dir=project.resources[resource_name],
70-
target_dir=self.resource_dir(project, resource_name),
71-
file_names=project.resource_file_names[resource_name],
72-
)
73-
74-
return res
39+
return self.kdist_dir / self.k_version / target_name
7540

7641
def kompile(self, project: Project, target_name: str) -> Path:
7742
self.kdist_dir.mkdir(parents=True, exist_ok=True)
78-
with FileLock(self.kdist_dir / '.lock'):
79-
for sub_project in project.sub_projects:
80-
self.sync(sub_project)
8143

44+
with FileLock(self.kdist_dir / '.lock'):
8245
output_dir = self.definition_dir(project, target_name)
8346

8447
if self.up_to_date(project, target_name):
8548
return output_dir
8649

87-
target = project.get_target(target_name)
88-
args = self.kompile_args(project, target)
89-
kompile(
90-
output_dir=output_dir,
91-
include_dirs=[self.include_dir(sub_project) for sub_project in project.sub_projects],
92-
cwd=self.kdist_dir,
93-
**args,
94-
)
50+
with KBuildEnv.create_temp(project) as env:
51+
env.kompile(target_name, output_dir)
9552

9653
return output_dir
9754

@@ -102,36 +59,92 @@ def up_to_date(self, project: Project, target_name: str) -> bool:
10259
if not timestamp.exists():
10360
return False
10461

105-
input_files: list[Path] = []
106-
for sub_project in project.sub_projects:
107-
input_files.append(sub_project.project_file)
108-
input_files.extend(self.source_files(sub_project))
109-
for resource_name in sub_project.resources:
110-
input_files.extend(self.resource_files(sub_project, resource_name))
111-
112-
input_timestamps = (input_file.stat().st_mtime for input_file in input_files)
62+
input_timestamps = (input_file.stat().st_mtime for input_file in project.all_files)
11363
target_timestamp = timestamp.stat().st_mtime
11464
return all(input_timestamp < target_timestamp for input_timestamp in input_timestamps)
11565

116-
def kompile_args(self, project: Project, target: Target) -> dict[str, Any]:
66+
67+
@final
68+
@dataclass(frozen=True)
69+
class KBuildEnv:
70+
project: Project
71+
path: Path
72+
73+
def __init__(self, project: Project, path: str | Path):
74+
path = Path(path).resolve()
75+
check_dir_path(path)
76+
object.__setattr__(self, 'project', project)
77+
object.__setattr__(self, 'path', path)
78+
79+
@staticmethod
80+
@contextmanager
81+
def create_temp(project: Project) -> Iterator[KBuildEnv]:
82+
with TemporaryDirectory(prefix=f'kbuild-{project.name}-') as path_str:
83+
env = KBuildEnv(project, path_str)
84+
env.sync()
85+
yield env
86+
87+
def sync(self) -> None:
88+
for sub_project in self.project.sub_projects:
89+
self._sync_project(sub_project)
90+
91+
def kompile(self, target_name: str, output_dir: Path) -> None:
92+
target = self.project.get_target(target_name)
93+
kompile(
94+
output_dir=output_dir,
95+
include_dirs=self._include_dirs,
96+
cwd=self.path,
97+
**self._kompile_args(target),
98+
)
99+
100+
@property
101+
def _include_dirs(self) -> list[Path]:
102+
return [self._include_dir(sub_project) for sub_project in self.project.sub_projects]
103+
104+
def _include_dir(self, project: Project) -> Path:
105+
return self.path / project.name / 'include'
106+
107+
def _source_dir(self, project: Project) -> Path:
108+
return self._include_dir(project) / project.name
109+
110+
def _resource_dir(self, project: Project, resource_name: str) -> Path:
111+
return self.path / project.name / 'resource' / resource_name
112+
113+
def _sync_project(self, project: Project) -> None:
114+
# Sync sources
115+
sync_files(
116+
source_dir=project.source_dir,
117+
target_dir=self._source_dir(project),
118+
file_names=project.source_file_names,
119+
)
120+
121+
# Sync resources
122+
for resource_name in project.resources:
123+
sync_files(
124+
source_dir=project.resources[resource_name],
125+
target_dir=self._resource_dir(project, resource_name),
126+
file_names=project.resource_file_names[resource_name],
127+
)
128+
129+
def _kompile_args(self, target: Target) -> dict[str, Any]:
117130
args = target.dict
118131
args.pop('name')
119-
args['main_file'] = self.source_dir(project) / args['main_file']
132+
args['main_file'] = self._source_dir(self.project) / args['main_file']
120133

121134
if 'ccopts' in args:
122-
args['ccopts'] = [self.render_opt(project, opt) for opt in args['ccopts']]
135+
args['ccopts'] = [self._render_opt(opt) for opt in args['ccopts']]
123136

124137
return args
125138

126-
def render_opt(self, project: Project, opt: str) -> str:
139+
def _render_opt(self, opt: str) -> str:
127140
def render(match: Match) -> str:
128141
project_name = match.group('project')
129142
resource_name = match.group('resource')
130143

131144
sub_project = single(
132-
sub_project for sub_project in project.sub_projects if sub_project.name == project_name
145+
sub_project for sub_project in self.project.sub_projects if sub_project.name == project_name
133146
)
134-
resource_path = self.resource_dir(sub_project, resource_name)
147+
resource_path = self._resource_dir(sub_project, resource_name)
135148

136149
if not resource_path.exists():
137150
raise ValueError('Failed to resolve opt {opt}: resource path {resource_path} does not exist')

src/pyk/kbuild/project.py

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from __future__ import annotations
22

33
import dataclasses
4+
from abc import ABC, abstractmethod
45
from dataclasses import dataclass
56
from functools import cached_property
6-
from pathlib import Path
7+
from importlib import resources
8+
from pathlib import Path, PosixPath
79
from typing import TYPE_CHECKING, final
810

911
import tomli
@@ -18,6 +20,41 @@
1820
from typing import Any
1921

2022

23+
class Source(ABC):
24+
@staticmethod
25+
def from_dict(dct: Mapping[str, Any]) -> Source:
26+
if 'path' in dct:
27+
return PathSource(Path(dct['path']))
28+
if 'package' in dct:
29+
return PackageSource(dct['package'])
30+
raise ValueError(f'Cannot parse source: {dct}')
31+
32+
@abstractmethod
33+
def resolve(self, project_path: Path) -> Path:
34+
...
35+
36+
37+
@final
38+
@dataclass(frozen=True)
39+
class PathSource(Source):
40+
path: Path
41+
42+
def resolve(self, project_path: Path) -> Path:
43+
return abs_or_rel_to(self.path, project_path)
44+
45+
46+
@final
47+
@dataclass(frozen=True)
48+
class PackageSource(Source):
49+
package: str
50+
51+
def resolve(self, project_path: Path) -> Path:
52+
traversable = resources.files(self.package)
53+
if not isinstance(traversable, PosixPath):
54+
raise ValueError(f'Package name {self.package!r} does not resolve to a directory')
55+
return traversable.resolve(strict=True)
56+
57+
2158
@final
2259
@dataclass(frozen=True)
2360
class Target:
@@ -164,24 +201,28 @@ def __init__(
164201
def load(project_file: str | Path) -> Project:
165202
project_file = Path(project_file)
166203
check_file_path(project_file)
204+
project_path = project_file.parent
167205

168-
with open(project_file, 'rb') as f:
169-
dct = tomli.load(f)
170-
171-
def _load_dependency(name: str, path: str) -> Project:
172-
dependency_path = abs_or_rel_to(Path(path), project_file.parent)
206+
def _load_dependency(name: str, dct: Any) -> Project:
207+
source = Source.from_dict(dct)
208+
dependency_path = source.resolve(project_path)
173209
project = Project.load_from_dir(dependency_path)
174210
if project.name != name:
175211
raise ValueError(f'Invalid dependency, expected name {name}, got: {project.name}')
176212
return project
177213

214+
with open(project_file, 'rb') as f:
215+
dct = tomli.load(f)
216+
178217
project = Project(
179-
path=project_file.parent,
218+
path=project_path,
180219
name=dct['project']['name'],
181220
version=dct['project']['version'],
182221
source_dir=dct['project']['source'],
183222
resources=dct['project'].get('resources'),
184-
dependencies=tuple(_load_dependency(name, path) for name, path in dct.get('dependencies', {}).items()),
223+
dependencies=tuple(
224+
_load_dependency(name, source_dct) for name, source_dct in dct.get('dependencies', {}).items()
225+
),
185226
targets=tuple(Target.from_dict(name, target) for name, target in dct.get('targets', {}).items()),
186227
)
187228

@@ -232,6 +273,16 @@ def resource_file_names(self) -> dict[str, list[str]]:
232273
for resource_name, resource_files in self.resource_files.items()
233274
}
234275

276+
@property
277+
def all_files(self) -> list[Path]:
278+
res: list[Path] = []
279+
for sub_project in self.sub_projects:
280+
res.append(sub_project.project_file)
281+
res.extend(sub_project.source_files)
282+
for resource_name in sub_project.resources:
283+
res.extend(sub_project.resource_files[resource_name])
284+
return res
285+
235286
def get_target(self, target_name: str) -> Target:
236287
# TODO Should be enforced as a validation rule
237288
return single(target for target in self.targets if target.name == target_name)

src/tests/profiling/test-data/kbuild/a-semantics/kbuild.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ version = "0.1.0"
44
source = "."
55

66
[dependencies]
7-
b-semantics = "../b-semantics"
8-
c-semantics = "../c-semantics"
7+
b-semantics = { path = "../b-semantics" }
8+
c-semantics = { path = "../c-semantics" }
99

1010
[targets.llvm]
1111
main-file = 'a.k'

0 commit comments

Comments
 (0)