@@ -8,9 +8,13 @@ import sys
8
8
import os
9
9
import filecmp
10
10
import shutil
11
+ import subprocess
11
12
import logging
12
13
13
14
from cam_config import ConfigCAM # CAM's configure structure
15
+ from atm_musica_config import MUSICA_CCPP_SCHEME_NAME
16
+ from atm_musica_config import MUSICA_REPO_URL , MUSICA_TAG
17
+ from atm_musica_config import CHEMISTRY_DATA_REPO_URL , CHEMISTRY_DATA_TAG
14
18
15
19
# Check for the CIME library, and add it
16
20
# to the python path:
@@ -27,6 +31,7 @@ from CIME.utils import stop_buffering_output
27
31
from CIME .buildlib import parse_input
28
32
from CIME .build import get_standard_makefile_args
29
33
from CIME .Tools .standard_script_setup import check_minimum_python_version
34
+ from CIME .locked_files import lock_file , unlock_file
30
35
#pylint: enable=wrong-import-position
31
36
32
37
check_minimum_python_version (3 , 7 ) #CAM requires version 3.7 or greater
@@ -73,7 +78,7 @@ def _build_cam():
73
78
# Re-run source generator in case registry, CCPP suites, or
74
79
# generator scripts have been modified, and
75
80
# to extract required source code paths:
76
- config .generate_cam_src (gen_indent )
81
+ scheme_names = config .generate_cam_src (gen_indent )
77
82
78
83
dycore = config .get_value ('dyn' )
79
84
reg_dir = config .get_value ('reg_dir' )
@@ -122,12 +127,6 @@ def _build_cam():
122
127
paths .append (os .path .join (atm_root , "src" , "dynamics" , "tests" ,
123
128
"initial_conditions" ))
124
129
125
- # If using the CMEPS/NUOPC coupler, then add additional path:
126
- if case .get_value ("COMP_INTERFACE" ) == "nuopc" :
127
- paths .append (os .path .join (__CIMEROOT , "src" , "drivers" ,
128
- "nuopc" , "nuopc_cap_share" ))
129
- # End if
130
-
131
130
# Write Filepath text file
132
131
with open (filepath_src , "w" , encoding = 'utf-8' ) as filepath :
133
132
filepath .write ("\n " .join (paths ))
@@ -174,11 +173,32 @@ def _build_cam():
174
173
if len (optional_mpas_features ) > 0 :
175
174
cmd += " OPTIONAL_MPAS_FEATURES=\" " + " " .join (optional_mpas_features ) + "\" "
176
175
176
+ # If MUSICA-CCPP scheme is used in a suite, download
177
+ # the MUSICA configuration and build the MUSICA library
178
+ if MUSICA_CCPP_SCHEME_NAME in scheme_names :
179
+ _download_musica_configuration (caseroot )
180
+ musica_install_path = _build_musica (caseroot )
181
+
182
+ cam_linked_libs = case .get_value ("CAM_LINKED_LIBS" )
183
+ musica_libs = "-lmusica-fortran -lmusica -lyaml-cpp"
184
+ if not musica_libs in cam_linked_libs :
185
+ _set_musica_lib_path (musica_install_path , caseroot )
186
+
187
+ cmd += ' USER_INCLDIR="' \
188
+ f'-I{ os .path .join (musica_install_path , "include" , "micm" )} ' \
189
+ f'-I{ os .path .join (musica_install_path , "include" , "musica" )} ' \
190
+ f'-I{ os .path .join (musica_install_path , "include" , "musica" , "micm" )} ' \
191
+ f'-I{ os .path .join (musica_install_path , "include" , "musica" , "tuvx" )} ' \
192
+ f'-I{ os .path .join (musica_install_path , "include" , "musica" , "fortran" )} ' \
193
+ '"'
194
+
177
195
retcode , out , err = run_cmd (cmd )
178
196
_LOGGER .info ("Command %s:\n \n stdout:\n %s\n \n stderr:\n %s\n " , cmd , out , err )
179
197
expect (retcode == 0 , f"Command { cmd } failed with rc={ retcode } " )
180
198
199
+ ###############################################################################
181
200
def _prepare_mpas (case : Case ) -> None :
201
+ ###############################################################################
182
202
"""
183
203
Prepare MPAS build infrastructure.
184
204
"""
@@ -206,7 +226,9 @@ def _prepare_mpas(case: Case) -> None:
206
226
shutil .copytree (os .path .normpath (os .path .join (mpas_dycore_src_root , os .pardir , os .pardir , "assets" )), mpas_dycore_bld_root , copy_function = _copy2_as_needed , dirs_exist_ok = True )
207
227
shutil .copytree (os .path .normpath (os .path .join (mpas_dycore_src_root , os .pardir , os .pardir , "driver" )), os .path .join (mpas_dycore_bld_root , "driver" ), copy_function = _copy2_as_needed , dirs_exist_ok = True )
208
228
229
+ ###############################################################################
209
230
def _copy2_as_needed (src : str , dst : str ) -> None :
231
+ ###############################################################################
210
232
"""
211
233
Wrapper around `shutil.copy2`. Copy the file `src` to the file or directory `dst` as needed.
212
234
"""
@@ -226,6 +248,184 @@ def _copy2_as_needed(src: str, dst: str) -> None:
226
248
# Example scenario: User added some new source code files.
227
249
shutil .copy2 (src , dst )
228
250
251
+ ###############################################################################
252
+ def _build_musica (clone_dest : str ) -> str :
253
+ ###############################################################################
254
+ """
255
+ Builds and installs the MUSICA library.
256
+
257
+ Args:
258
+ clone_dest: destination where the repository will be cloned
259
+
260
+ Raises:
261
+ Exception: If configuring the CMake MUSICA project fails or
262
+ the MUSICA library build fails, an exception is raised.
263
+
264
+ Returns:
265
+ musica_install_path: path to the MUSICA installation directory
266
+ """
267
+ _clone_and_checkout (MUSICA_REPO_URL , MUSICA_TAG , clone_dest )
268
+
269
+ bld_path = os .path .join (clone_dest , "musica" , "build" )
270
+ if os .path .exists (bld_path ):
271
+ shutil .rmtree (bld_path )
272
+ os .makedirs (bld_path )
273
+
274
+ install_dir = "install"
275
+ command = [
276
+ "cmake" ,
277
+ f"-D CMAKE_INSTALL_PREFIX={ install_dir } " ,
278
+ "-D CMAKE_BUILD_TYPE=Release" ,
279
+ "-D MUSICA_ENABLE_TESTS=OFF" ,
280
+ "-D MUSICA_BUILD_FORTRAN_INTERFACE=ON" ,
281
+ ".."
282
+ ]
283
+ try :
284
+ subprocess .run (command , cwd = bld_path , stdout = subprocess .PIPE ,
285
+ stderr = subprocess .PIPE , text = True , check = False )
286
+ except subprocess .CalledProcessError as e :
287
+ raise subprocess .CalledProcessError (e .returncode , e .cmd , "The subprocess \
288
+ for cmake to configure the MUSICA CMake project failed." ) from e
289
+ except FileNotFoundError as e :
290
+ raise FileNotFoundError ("The 'cmake' command was not found." ) from e
291
+ except OSError as e :
292
+ raise OSError ("An error occurred while executing the 'cmake' command." ) from e
293
+
294
+ command = ["cmake" , "--build" , "." , "--target" , "install" ]
295
+ try :
296
+ subprocess .run (command , cwd = bld_path , stdout = subprocess .PIPE ,
297
+ stderr = subprocess .PIPE , text = True , check = False )
298
+ except subprocess .CalledProcessError as e :
299
+ raise subprocess .CalledProcessError (e .returncode , e .cmd , "The subprocess \
300
+ for cmake to build the MUSICA library failed." ) from e
301
+ except FileNotFoundError as e :
302
+ raise FileNotFoundError ("The 'cmake' command was not found." ) from e
303
+ except OSError as e :
304
+ raise OSError ("An error occurred while executing the 'cmake' command." ) from e
305
+
306
+ musica_install_path = os .path .join (bld_path , install_dir )
307
+
308
+ return musica_install_path
309
+
310
+ ###############################################################################
311
+ def _download_musica_configuration (download_dest : str ) -> None :
312
+ ###############################################################################
313
+ """
314
+ Downloads the MUSICA configuration and renames the configuration
315
+ directory to match the name in the MUSICA-CCPP configuration.
316
+
317
+ Args:
318
+ download_dest: destination where configuration will be downloaded
319
+
320
+ Raises:
321
+ Exception: If the directory to be renamed is not found or
322
+ any other exceptions occur during the renaming process,
323
+ an exception is raised with the error message.
324
+ """
325
+ musica_config_dir_name = "musica_configurations"
326
+
327
+ _clone_and_checkout (CHEMISTRY_DATA_REPO_URL , CHEMISTRY_DATA_TAG , download_dest )
328
+
329
+ original_dir = os .path .join (download_dest , "cam-sima-chemistry-data" , "mechanisms" )
330
+ renamed_dir = os .path .join (download_dest , "cam-sima-chemistry-data" , musica_config_dir_name )
331
+ try :
332
+ os .rename (original_dir , renamed_dir )
333
+ except FileNotFoundError as e :
334
+ raise FileNotFoundError (f"The directory '{ original_dir } ' was not found." ) from e
335
+ except FileExistsError as e :
336
+ raise FileExistsError (f"The destination directory '{ renamed_dir } ' already exists." ) from e
337
+ except PermissionError as e :
338
+ raise PermissionError (f"Permission denied to rename '{ original_dir } '." ) from e
339
+ except OSError as e :
340
+ raise OSError ("An error occurred while renaming." ) from e
341
+
342
+ musica_config_path = os .path .join (download_dest , musica_config_dir_name )
343
+ if os .path .exists (musica_config_path ):
344
+ shutil .rmtree (musica_config_path )
345
+
346
+ shutil .move (renamed_dir , download_dest )
347
+
348
+ ###############################################################################
349
+ def _set_musica_lib_path (musica_install_path : str , caseroot : str ) -> None :
350
+ ###############################################################################
351
+ """
352
+ Sets the MUSICA libraries path to CAM_LINKED_LIBS, allowing the libraries
353
+ to be linked during the CESM build process.
354
+
355
+ Args:
356
+ musica_install_path: path to the MUSICA installation directory
357
+ caseroot: CASEROOT where the xmlchange command is located
358
+
359
+ Raises:
360
+ Exception: If the subprocess for the xmlchange command fails,
361
+ an exception is raised with the error message.
362
+ """
363
+
364
+ unlock_file ("env_build.xml" , caseroot )
365
+
366
+ command = [
367
+ "./xmlchange" ,
368
+ "--append" ,
369
+ # The libraries must be on the same line because CIME flags an
370
+ # error for multi-character arguments preceded by a single dash
371
+ f"CAM_LINKED_LIBS=-L{ os .path .join (musica_install_path , 'lib64' )} -lmusica-fortran -lmusica -lyaml-cpp"
372
+ ]
373
+ try :
374
+ subprocess .run (command , cwd = caseroot , stdout = subprocess .PIPE ,
375
+ stderr = subprocess .PIPE , text = True , check = False )
376
+ except subprocess .CalledProcessError as e :
377
+ raise subprocess .CalledProcessError (e .returncode , e .cmd , "The subprocess \
378
+ for xmlchange to set the MUSICA library path failed." ) from e
379
+ except FileNotFoundError as e :
380
+ raise FileNotFoundError ("The 'xmlchange' command was not found." ) from e
381
+ except OSError as e :
382
+ raise OSError ("An error occurred while executing the 'xmlchange' command." ) from e
383
+
384
+ lock_file ("env_build.xml" , caseroot )
385
+
386
+ ###############################################################################
387
+ def _clone_and_checkout (repo_url : str , tag_name : str , clone_dest : str ) -> None :
388
+ ###############################################################################
389
+ """
390
+ Clones a Git repository from the URL and checks out a specific branch.
391
+
392
+ Args:
393
+ repo_url: URL of the Git repository to clone
394
+ tag_name: tag name to check out
395
+ clone_dest: destination where the repository will be cloned
396
+
397
+ Raises:
398
+ Exception: If the `git clone` or `git checkout` commands fail,
399
+ an exception is raised with the error message.
400
+ """
401
+ repo_name = repo_url .split ("/" )[- 1 ].replace (".git" , "" )
402
+ repo_path = os .path .join (clone_dest , repo_name )
403
+
404
+ if os .path .exists (repo_path ):
405
+ shutil .rmtree (repo_path )
406
+
407
+ try :
408
+ subprocess .run (["git" , "clone" , repo_url , repo_path ],
409
+ stdout = subprocess .PIPE , stderr = subprocess .PIPE , text = True , check = False )
410
+ except subprocess .CalledProcessError as e :
411
+ raise subprocess .CalledProcessError (e .returncode , e .cmd , f"The subprocess \
412
+ for git to clone the repository { repo_url } failed." ) from e
413
+ except FileNotFoundError as e :
414
+ raise FileNotFoundError ("The 'git' command was not found." ) from e
415
+ except OSError as e :
416
+ raise OSError ("An error occurred while executing the 'git' command." ) from e
417
+
418
+ try :
419
+ subprocess .run (["git" , "-C" , repo_path , "checkout" , tag_name ],
420
+ stdout = subprocess .PIPE , stderr = subprocess .PIPE , text = True , check = False )
421
+ except subprocess .CalledProcessError as e :
422
+ raise subprocess .CalledProcessError (e .returncode , e .cmd , f"The subprocess \
423
+ for git to checkout the branch { tag_name } failed." ) from e
424
+ except FileNotFoundError as e :
425
+ raise FileNotFoundError ("The 'git' command was not found." ) from e
426
+ except OSError as e :
427
+ raise OSError ("An error occurred while executing the 'git' command." ) from e
428
+
229
429
###############################################################################
230
430
231
431
if __name__ == "__main__" :
0 commit comments