71
71
import subprocess
72
72
import sys
73
73
from pathlib import Path
74
+ from textwrap import dedent
74
75
from typing import NamedTuple
75
76
76
77
import tomlkit
77
78
import tomlkit .items
78
79
79
80
80
- class WorkingDirectoryPaths (NamedTuple ):
81
- """Paths within the temporary working directory"""
82
-
83
- working_path : Path
84
- working_pyproject_file : Path
85
- working_environment_file : Path
86
- working_environment_blas_file : Path
87
- pixi_toml_raw_file : Path
88
- gitignore_file : Path
89
-
90
-
91
81
RAW_COMMAND = """
92
82
pixi exec conda-lock render-lock-spec \
93
83
--channel=conda-forge \
@@ -103,6 +93,27 @@ class WorkingDirectoryPaths(NamedTuple):
103
93
PARSED_COMMAND = shlex .split (RAW_COMMAND )
104
94
105
95
96
+ class OriginalPaths (NamedTuple ):
97
+ """Paths to the original files"""
98
+
99
+ project_root : Path # ./
100
+ pyproject_file : Path # ./pyproject.toml
101
+ environment_file : Path # ./environment.yml
102
+ environment_blas_file : Path # ./scripts/environment-blas.yml
103
+ pixi_toml_file : Path # ./pixi.toml
104
+
105
+
106
+ class WorkingDirectoryPaths (NamedTuple ):
107
+ """Paths within the temporary working directory"""
108
+
109
+ working_path : Path # ./pixi-working/
110
+ pyproject_file : Path # ./pixi-working/pyproject.toml
111
+ environment_file : Path # ./pixi-working/environment.yml
112
+ environment_blas_file : Path # ./pixi-working/scripts/environment-blas.yml
113
+ pixi_toml_file : Path # ./pixi-working/pixi.toml
114
+ gitignore_file : Path # ./pixi-working/.gitignore
115
+
116
+
106
117
def main ():
107
118
parser = argparse .ArgumentParser (
108
119
description = "Generate pixi.toml from environment files"
@@ -119,100 +130,140 @@ def main():
119
130
print ("pixi is not installed. See <https://pixi.sh/latest/#installation>" ) # noqa: T201
120
131
sys .exit (1 )
121
132
122
- current_dir = Path (__file__ ).parent
123
- assert current_dir .name == "scripts"
124
- project_root = current_dir .parent
125
-
126
- pyproject_file = project_root / "pyproject.toml"
127
-
128
- pyproject_data = tomlkit .loads (pyproject_file .read_text ())
129
-
130
- pyproject_data = preprocess_pyproject_data (pyproject_data )
133
+ original_paths = get_original_paths ()
131
134
132
135
# Make temporary working directory
133
- working_path = project_root / "pixi-working"
134
- working_path .mkdir (parents = True , exist_ok = True )
135
- gitignore_file = working_path / ".gitignore"
136
- gitignore_file .write_text ("*" )
136
+ working_directory_paths = initialize_working_directory (original_paths )
137
137
138
- working_pyproject_file = working_path / "pyproject.toml"
139
- with working_pyproject_file .open ("w" ) as fh :
138
+ # Copy the processed pyproject.toml to the working directory
139
+ pyproject_data = tomlkit .loads (original_paths .pyproject_file .read_text ())
140
+ pyproject_data = preprocess_pyproject_data (pyproject_data )
141
+ with working_directory_paths .pyproject_file .open ("w" ) as fh :
140
142
tomlkit .dump (pyproject_data , fh )
141
143
142
- raw_environment_file = project_root / "environment.yml"
143
- raw_environment_data = raw_environment_file .read_text ()
144
-
144
+ # Copy the processed environment file to the working directory
145
+ raw_environment_data = original_paths .environment_file .read_text ()
145
146
environment_data = preprocess_environment_data (raw_environment_data )
146
-
147
- working_environment_file = working_path / "environment.yml"
148
- with working_environment_file .open ("w" ) as fh :
147
+ with working_directory_paths .environment_file .open ("w" ) as fh :
149
148
fh .write (environment_data )
150
149
151
- environment_blas_file = project_root / "scripts" / "environment-blas.yml"
152
- working_environment_blas_file = working_path / "scripts" / "environment-blas.yml"
150
+ # Copy environment-blas.yml to the working directory
153
151
# This is for our exclusive use, so it doesn't need to be preprocessed.
154
- working_environment_blas_file .parent .mkdir (parents = True , exist_ok = True )
155
- shutil .copy (environment_blas_file , working_environment_blas_file )
152
+ working_directory_paths .environment_blas_file .parent .mkdir (
153
+ parents = True , exist_ok = True
154
+ )
155
+ shutil .copy (
156
+ original_paths .environment_blas_file ,
157
+ working_directory_paths .environment_blas_file ,
158
+ )
156
159
160
+ # Run conda-lock to generate the pixi.toml file
157
161
print (f"Running the command:\n { shlex .join (PARSED_COMMAND )} \n " ) # noqa: T201
158
162
result = subprocess .run (
159
- PARSED_COMMAND , check = True , capture_output = True , cwd = working_path
163
+ PARSED_COMMAND ,
164
+ check = True ,
165
+ capture_output = True ,
166
+ cwd = working_directory_paths .working_path ,
160
167
)
161
168
162
169
warnings = result .stderr .decode ("utf-8" )
163
170
if warnings :
164
171
print (f"Warnings: \n { warnings } \n " ) # noqa: T201
165
172
173
+ # Write the unprocessed pixi.toml data to the working directory
166
174
pixi_toml_raw_data = tomlkit .loads (result .stdout .decode ("utf-8" ))
167
-
168
- pixi_toml_raw_file = working_path / "pixi.toml"
169
- pixi_toml_raw_file .write_text (tomlkit .dumps (pixi_toml_raw_data ))
170
-
171
- pixi_toml_data = postprocess_pixi_toml_data (pixi_toml_raw_data )
172
-
173
- working_directory_paths = WorkingDirectoryPaths (
174
- working_path = working_path ,
175
- working_pyproject_file = working_pyproject_file ,
176
- working_environment_file = working_environment_file ,
177
- working_environment_blas_file = working_environment_blas_file ,
178
- pixi_toml_raw_file = pixi_toml_raw_file ,
179
- gitignore_file = gitignore_file ,
180
- )
175
+ pixi_toml_raw_content = tomlkit .dumps (pixi_toml_raw_data )
176
+ working_directory_paths .pixi_toml_file .write_text (pixi_toml_raw_content )
181
177
182
178
# Generate the pixi.toml content
179
+ pixi_toml_data = postprocess_pixi_toml_data (pixi_toml_raw_data )
183
180
pixi_toml_content = tomlkit .dumps (pixi_toml_data )
184
181
185
182
if args .verify_only :
186
183
# Compare with existing pixi.toml
187
- pixi_toml_file = project_root / "pixi.toml"
188
- if not pixi_toml_file .exists ():
189
- print ("ERROR: pixi.toml does not exist" ) # noqa: T201
190
- cleanup_working_directory (working_directory_paths )
191
- sys .exit (1 )
192
-
193
- existing_content = pixi_toml_file .read_text ()
194
- if existing_content != pixi_toml_content :
195
- print ("ERROR: pixi.toml is not consistent with environment files" ) # noqa: T201
196
- print ("Run 'python scripts/generate-pixi-toml.py' to update it" ) # noqa: T201
197
- cleanup_working_directory (working_directory_paths )
184
+ existing_pixi_toml_content = original_paths .pixi_toml_file .read_text ()
185
+ if existing_pixi_toml_content != pixi_toml_content :
186
+ message = dedent ("""\
187
+ Mismatch detected between existing and new pixi.toml content.
188
+
189
+ New pixi.toml content:
190
+ {pixi_toml_content}
191
+
192
+ Run 'python scripts/generate-pixi-toml.py' to regenerate it.
193
+ After updating `pixi.toml`, it's suggested to run the following commands:
194
+
195
+ pixi lock
196
+ git add pixi.toml
197
+ git commit -m "Regenerate pixi.toml"
198
+ git add pixi.lock
199
+ git commit -m "Update pixi lockfile"
200
+
201
+ ERROR: pixi.toml is not consistent with environment files.
202
+ See above for details.
203
+ """ ).format (pixi_toml_content = pixi_toml_content )
204
+ print (message ) # noqa: T201
198
205
sys .exit (1 )
199
206
200
207
print ("SUCCESS: pixi.toml is consistent with environment files" ) # noqa: T201
201
208
cleanup_working_directory (working_directory_paths )
202
209
sys .exit (0 )
203
210
else :
204
211
# Write the pixi.toml file to the project root
205
- ( project_root / "pixi.toml" ) .write_text (pixi_toml_content )
212
+ original_paths . pixi_toml_file .write_text (pixi_toml_content )
206
213
cleanup_working_directory (working_directory_paths )
207
214
208
215
216
+ def get_original_paths () -> OriginalPaths :
217
+ """Get the paths to the original files"""
218
+ current_dir = Path (__file__ ).parent
219
+ assert current_dir .name == "scripts"
220
+ project_root = current_dir .parent
221
+ pyproject_file = project_root / "pyproject.toml"
222
+ environment_file = project_root / "environment.yml"
223
+ environment_blas_file = project_root / "scripts" / "environment-blas.yml"
224
+ pixi_toml_file = project_root / "pixi.toml"
225
+ if not pixi_toml_file .exists ():
226
+ raise FileNotFoundError (f"pixi.toml does not exist at { pixi_toml_file } " )
227
+ return OriginalPaths (
228
+ project_root = project_root ,
229
+ pyproject_file = pyproject_file ,
230
+ environment_file = environment_file ,
231
+ environment_blas_file = environment_blas_file ,
232
+ pixi_toml_file = pixi_toml_file ,
233
+ )
234
+
235
+
236
+ def initialize_working_directory (
237
+ original_paths : OriginalPaths ,
238
+ ) -> WorkingDirectoryPaths :
239
+ """Initialize the temporary working directory"""
240
+ working_path = original_paths .project_root / "pixi-working"
241
+ working_path .mkdir (parents = True , exist_ok = True )
242
+ gitignore_file = working_path / ".gitignore"
243
+ gitignore_file .write_text ("*" )
244
+
245
+ pyproject_file = working_path / "pyproject.toml"
246
+ environment_file = working_path / "environment.yml"
247
+ environment_blas_file = working_path / "scripts" / "environment-blas.yml"
248
+ pixi_toml_file = working_path / "pixi.toml"
249
+
250
+ return WorkingDirectoryPaths (
251
+ working_path = working_path ,
252
+ pyproject_file = pyproject_file ,
253
+ environment_file = environment_file ,
254
+ environment_blas_file = environment_blas_file ,
255
+ pixi_toml_file = pixi_toml_file ,
256
+ gitignore_file = gitignore_file ,
257
+ )
258
+
259
+
209
260
def cleanup_working_directory (working_paths : WorkingDirectoryPaths ):
210
261
"""Clean up the temporary working directory and files"""
211
- working_paths .working_pyproject_file .unlink ()
212
- working_paths .working_environment_file .unlink ()
213
- working_paths .working_environment_blas_file .unlink ()
214
- working_paths .working_environment_blas_file .parent .rmdir ()
215
- working_paths .pixi_toml_raw_file .unlink ()
262
+ working_paths .pyproject_file .unlink ()
263
+ working_paths .environment_file .unlink ()
264
+ working_paths .environment_blas_file .unlink ()
265
+ working_paths .environment_blas_file .parent .rmdir ()
266
+ working_paths .pixi_toml_file .unlink ()
216
267
working_paths .gitignore_file .unlink ()
217
268
working_paths .working_path .rmdir ()
218
269
0 commit comments