1
1
from __future__ import annotations
2
2
3
3
import re
4
+ from contextlib import contextmanager
4
5
from dataclasses import dataclass
5
6
from functools import cached_property
6
7
from pathlib import Path
8
+ from tempfile import TemporaryDirectory
7
9
from typing import TYPE_CHECKING , final
8
10
9
11
from filelock import FileLock
10
12
11
13
from ..ktool .kompile import kompile
12
- from ..utils import single
14
+ from ..utils import check_dir_path , single
13
15
from .utils import k_version , sync_files
14
16
15
17
if TYPE_CHECKING :
18
+ from collections .abc import Iterator
16
19
from re import Match
17
20
from typing import Any
18
21
@@ -33,65 +36,19 @@ def k_version(self) -> str:
33
36
return k_version ().text
34
37
35
38
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
75
40
76
41
def kompile (self , project : Project , target_name : str ) -> Path :
77
42
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 )
81
43
44
+ with FileLock (self .kdist_dir / '.lock' ):
82
45
output_dir = self .definition_dir (project , target_name )
83
46
84
47
if self .up_to_date (project , target_name ):
85
48
return output_dir
86
49
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 )
95
52
96
53
return output_dir
97
54
@@ -102,36 +59,92 @@ def up_to_date(self, project: Project, target_name: str) -> bool:
102
59
if not timestamp .exists ():
103
60
return False
104
61
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 )
113
63
target_timestamp = timestamp .stat ().st_mtime
114
64
return all (input_timestamp < target_timestamp for input_timestamp in input_timestamps )
115
65
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 ]:
117
130
args = target .dict
118
131
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' ]
120
133
121
134
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' ]]
123
136
124
137
return args
125
138
126
- def render_opt (self , project : Project , opt : str ) -> str :
139
+ def _render_opt (self , opt : str ) -> str :
127
140
def render (match : Match ) -> str :
128
141
project_name = match .group ('project' )
129
142
resource_name = match .group ('resource' )
130
143
131
144
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
133
146
)
134
- resource_path = self .resource_dir (sub_project , resource_name )
147
+ resource_path = self ._resource_dir (sub_project , resource_name )
135
148
136
149
if not resource_path .exists ():
137
150
raise ValueError ('Failed to resolve opt {opt}: resource path {resource_path} does not exist' )
0 commit comments