Skip to content

Commit f7648f0

Browse files
Merge pull request #1779 from OceanParcels/v/1637
Cleanup macOS debugging files
2 parents e709843 + df1b0a0 commit f7648f0

File tree

6 files changed

+101
-96
lines changed

6 files changed

+101
-96
lines changed

parcels/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
__version__ = version
44

5-
import parcels.rng as ParcelsRandom # noqa
5+
import parcels.rng as ParcelsRandom # noqa: F401
66
from parcels.application_kernels import *
77
from parcels.field import *
88
from parcels.fieldset import *

parcels/_version.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os
22
import subprocess
3+
import warnings
34

45
try:
56
from parcels._version_setup import version
@@ -13,9 +14,11 @@
1314
.decode("ascii")
1415
.strip()
1516
)
16-
except subprocess.SubprocessError as e:
17-
e.add_note(
17+
except subprocess.SubprocessError:
18+
warnings.warn(
1819
"Looks like you're trying to do a development install of parcels. "
1920
"This needs to be in a git repo so that version information is available. "
21+
"Setting version to 'unknown'.",
22+
stacklevel=2,
2023
)
21-
raise e
24+
version = "unknown"

parcels/kernel.py

Lines changed: 55 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import functools
55
import hashlib
66
import inspect
7-
import math # noqa
7+
import math # noqa: F401
88
import os
9-
import random # noqa
9+
import random # noqa: F401
10+
import shutil
1011
import sys
1112
import textwrap
1213
import types
@@ -17,10 +18,9 @@
1718

1819
import numpy as np
1920
import numpy.ctypeslib as npct
20-
from numpy import ndarray
2121

22-
import parcels.rng as ParcelsRandom # noqa
23-
from parcels import rng # noqa
22+
import parcels.rng as ParcelsRandom # noqa: F401
23+
from parcels import rng # noqa: F401
2424
from parcels._compat import MPI
2525
from parcels.application_kernels.advection import (
2626
AdvectionAnalytical,
@@ -76,19 +76,14 @@ def __init__(
7676
self.funcvars = funcvars
7777
self.funccode = funccode
7878
self.py_ast = py_ast
79-
self.dyn_srcs = []
80-
self.src_file = None
81-
self.lib_file = None
82-
self.log_file = None
79+
self.src_file: str | None = None
80+
self.lib_file: str | None = None
81+
self.log_file: str | None = None
8382
self.scipy_positionupdate_kernels_added = False
8483

8584
# Generate the kernel function and add the outer loop
8685
if self._ptype.uses_jit:
87-
src_file_or_files, self.lib_file, self.log_file = self.get_kernel_compile_files()
88-
if type(src_file_or_files) in (list, dict, tuple, ndarray):
89-
self.dyn_srcs = src_file_or_files
90-
else:
91-
self.src_file = src_file_or_files
86+
self.src_file, self.lib_file, self.log_file = self.get_kernel_compile_files()
9287

9388
def __del__(self):
9489
# Clean-up the in-memory dynamic linked libraries.
@@ -139,7 +134,10 @@ def remove_deleted(self, pset):
139134
pset.remove_indices(indices)
140135

141136
@abc.abstractmethod
142-
def get_kernel_compile_files(self): ...
137+
def get_kernel_compile_files(self) -> tuple[str, str, str]: ...
138+
139+
@abc.abstractmethod
140+
def remove_lib(self) -> None: ...
143141

144142

145143
class Kernel(BaseKernel):
@@ -272,25 +270,7 @@ def __init__(
272270
c_include_str = self._c_include
273271
self.ccode = loopgen.generate(self.funcname, self.field_args, self.const_args, kernel_ccode, c_include_str)
274272

275-
src_file_or_files, self.lib_file, self.log_file = self.get_kernel_compile_files()
276-
if type(src_file_or_files) in (list, dict, tuple, np.ndarray):
277-
self.dyn_srcs = src_file_or_files
278-
else:
279-
self.src_file = src_file_or_files
280-
281-
def __del__(self):
282-
# Clean-up the in-memory dynamic linked libraries.
283-
# This is not really necessary, as these programs are not that large, but with the new random
284-
# naming scheme which is required on Windows OS'es to deal with updates to a Parcels' kernel.
285-
try:
286-
self.remove_lib()
287-
except:
288-
pass
289-
self._fieldset = None
290-
self.field_args = None
291-
self.const_args = None
292-
self.funcvars = None
293-
self.funccode = None
273+
self.src_file, self.lib_file, self.log_file = self.get_kernel_compile_files()
294274

295275
@property
296276
def ptype(self):
@@ -330,9 +310,9 @@ def Setcoords(particle, fieldset, time):
330310
particle.time = particle.time_nextloop
331311

332312
def Updatecoords(particle, fieldset, time):
333-
particle.lon_nextloop = particle.lon + particle_dlon # noqa
334-
particle.lat_nextloop = particle.lat + particle_dlat # noqa
335-
particle.depth_nextloop = particle.depth + particle_ddepth # noqa
313+
particle.lon_nextloop = particle.lon + particle_dlon # type: ignore[name-defined] # noqa
314+
particle.lat_nextloop = particle.lat + particle_dlat # type: ignore[name-defined] # noqa
315+
particle.depth_nextloop = particle.depth + particle_ddepth # type: ignore[name-defined] # noqa
336316
particle.time_nextloop = particle.time + particle.dt
337317

338318
self._pyfunc = (Setcoords + self + Updatecoords)._pyfunc
@@ -412,29 +392,22 @@ def remove_lib(self):
412392
del self._lib
413393
self._lib = None
414394

415-
all_files_array = []
416-
if self.src_file is None:
417-
if self.dyn_srcs is not None:
418-
[all_files_array.append(fpath) for fpath in self.dyn_srcs]
419-
else:
420-
if self.src_file is not None:
421-
all_files_array.append(self.src_file)
395+
all_files: list[str] = []
396+
if self.src_file is not None:
397+
all_files.append(self.src_file)
422398
if self.log_file is not None:
423-
all_files_array.append(self.log_file)
424-
if self.lib_file is not None and all_files_array is not None and self.delete_cfiles is not None:
425-
self.cleanup_remove_files(self.lib_file, all_files_array, self.delete_cfiles)
399+
all_files.append(self.log_file)
400+
if self.lib_file is not None:
401+
self.cleanup_remove_files(self.lib_file, all_files, self.delete_cfiles)
426402

427403
# If file already exists, pull new names. This is necessary on a Windows machine, because
428404
# Python's ctype does not deal in any sort of manner well with dynamic linked libraries on this OS.
429405
if self._ptype.uses_jit:
430-
src_file_or_files, self.lib_file, self.log_file = self.get_kernel_compile_files()
431-
if type(src_file_or_files) in (list, dict, tuple, ndarray):
432-
self.dyn_srcs = src_file_or_files
433-
else:
434-
self.src_file = src_file_or_files
406+
self.src_file, self.lib_file, self.log_file = self.get_kernel_compile_files()
435407

436408
def get_kernel_compile_files(self):
437409
"""Returns the correct src_file, lib_file, log_file for this kernel."""
410+
basename: str
438411
if MPI:
439412
mpi_comm = MPI.COMM_WORLD
440413
mpi_rank = mpi_comm.Get_rank()
@@ -453,39 +426,26 @@ def get_kernel_compile_files(self):
453426
dyn_dir = get_cache_dir()
454427
basename = f"{cache_name}_0"
455428
lib_path = "lib" + basename
456-
src_file_or_files = None
457-
if type(basename) in (list, dict, tuple, ndarray):
458-
src_file_or_files = [""] * len(basename)
459-
for i, src_file in enumerate(basename):
460-
src_file_or_files[i] = f"{os.path.join(dyn_dir, src_file)}.c"
461-
else:
462-
src_file_or_files = f"{os.path.join(dyn_dir, basename)}.c"
429+
430+
assert isinstance(basename, str)
431+
432+
src_file = f"{os.path.join(dyn_dir, basename)}.c"
463433
lib_file = f"{os.path.join(dyn_dir, lib_path)}.{'dll' if sys.platform == 'win32' else 'so'}"
464434
log_file = f"{os.path.join(dyn_dir, basename)}.log"
465-
return src_file_or_files, lib_file, log_file
435+
return src_file, lib_file, log_file
466436

467437
def compile(self, compiler):
468438
"""Writes kernel code to file and compiles it."""
469-
all_files_array = []
470439
if self.src_file is None:
471-
if self.dyn_srcs is not None:
472-
for dyn_src in self.dyn_srcs:
473-
with open(dyn_src, "w") as f:
474-
f.write(self.ccode)
475-
all_files_array.append(dyn_src)
476-
compiler.compile(self.dyn_srcs, self.lib_file, self.log_file)
477-
else:
478-
if self.src_file is not None:
479-
with open(self.src_file, "w") as f:
480-
f.write(self.ccode)
481-
if self.src_file is not None:
482-
all_files_array.append(self.src_file)
483-
compiler.compile(self.src_file, self.lib_file, self.log_file)
484-
if len(all_files_array) > 0:
485-
if self.delete_cfiles is False:
486-
logger.info(f"Compiled {self.name} ==> {self.src_file}")
487-
if self.log_file is not None:
488-
all_files_array.append(self.log_file)
440+
return
441+
442+
with open(self.src_file, "w") as f:
443+
f.write(self.ccode)
444+
445+
compiler.compile(self.src_file, self.lib_file, self.log_file)
446+
447+
if self.delete_cfiles is False:
448+
logger.info(f"Compiled {self.name} ==> {self.src_file}")
489449

490450
def load_lib(self):
491451
self._lib = npct.load_library(self.lib_file, ".")
@@ -558,14 +518,22 @@ def from_list(cls, fieldset, ptype, pyfunc_list, *args, **kwargs):
558518
return functools.reduce(lambda x, y: x + y, pyfunc_list)
559519

560520
@staticmethod
561-
def cleanup_remove_files(lib_file, all_files_array, delete_cfiles):
562-
if lib_file is not None:
563-
if os.path.isfile(lib_file): # and delete_cfiles
564-
os.remove(lib_file)
565-
if delete_cfiles:
566-
for s in all_files_array:
567-
if os.path.exists(s):
568-
os.remove(s)
521+
def cleanup_remove_files(lib_file: str | None, all_files: list[str], delete_cfiles: bool) -> None:
522+
if lib_file is None:
523+
return
524+
525+
# Remove compiled files
526+
if os.path.isfile(lib_file):
527+
os.remove(lib_file)
528+
529+
macos_debugging_files = f"{lib_file}.dSYM"
530+
if os.path.isdir(macos_debugging_files):
531+
shutil.rmtree(macos_debugging_files)
532+
533+
if delete_cfiles:
534+
for s in all_files:
535+
if os.path.exists(s):
536+
os.remove(s)
569537

570538
@staticmethod
571539
def cleanup_unload_lib(lib):

parcels/tools/global_statics.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
import sys
44
from pathlib import Path
55
from tempfile import gettempdir
6+
from typing import Literal
67

8+
USER_ID: int | Literal["tmp"]
79
try:
810
from os import getuid
11+
12+
USER_ID = getuid()
913
except:
10-
# Windows does not have getuid(), so define to simply return 'tmp'
11-
def getuid(): # type: ignore
12-
return "tmp"
14+
# Windows does not have getuid()
15+
USER_ID = "tmp"
1316

1417

1518
__all__ = ["cleanup_remove_files", "cleanup_unload_lib", "get_package_dir", "get_cache_dir"]
@@ -34,6 +37,6 @@ def get_package_dir():
3437

3538

3639
def get_cache_dir():
37-
directory = os.path.join(gettempdir(), f"parcels-{getuid()}")
40+
directory = os.path.join(gettempdir(), f"parcels-{USER_ID}")
3841
Path(directory).mkdir(exist_ok=True)
3942
return directory

tests/test_kernel_execution.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import os
22
import sys
3+
import uuid
4+
from datetime import timedelta
35

46
import numpy as np
57
import pytest
@@ -15,11 +17,23 @@
1517
StatusCode,
1618
)
1719
from tests.common_kernels import DeleteParticle, DoNothing, MoveEast, MoveNorth
18-
from tests.utils import create_fieldset_unit_mesh
20+
from tests.utils import assert_empty_folder, create_fieldset_unit_mesh, create_fieldset_zeros_simple
1921

2022
ptype = {"scipy": ScipyParticle, "jit": JITParticle}
2123

2224

25+
@pytest.fixture()
26+
def parcels_cache(monkeypatch, tmp_path_factory):
27+
"""Dedicated folder parcels used to store cached Kernel C code/libraries and log files."""
28+
tmp_path = tmp_path_factory.mktemp(f"c-code-{uuid.uuid4()}")
29+
30+
def fake_get_cache_dir():
31+
return tmp_path
32+
33+
monkeypatch.setattr(parcels.kernel, "get_cache_dir", fake_get_cache_dir)
34+
yield tmp_path
35+
36+
2337
@pytest.fixture
2438
def fieldset_unit_mesh():
2539
return create_fieldset_unit_mesh()
@@ -427,3 +441,16 @@ def outdated_kernel(particle, fieldset, time, dt):
427441
pset.execute(outdated_kernel, endtime=1.0, dt=1.0)
428442

429443
assert "Since Parcels v2.0" in str(e.value)
444+
445+
446+
def test_kernel_file_cleanup(parcels_cache):
447+
pset = ParticleSet(create_fieldset_zeros_simple(), pclass=JITParticle, lon=[0.0], lat=[0.0])
448+
449+
pset.execute(
450+
[parcels.AdvectionRK4],
451+
runtime=timedelta(minutes=10),
452+
dt=timedelta(minutes=5),
453+
)
454+
del pset # cleans up compiled C files on deletion
455+
456+
assert_empty_folder(parcels_cache)

tests/utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,7 @@ def create_fieldset_zeros_simple(xdim=40, ydim=100):
8181
data = {"U": np.array(U, dtype=np.float32), "V": np.array(V, dtype=np.float32)}
8282
dimensions = {"lat": lat, "lon": lon, "depth": depth}
8383
return FieldSet.from_data(data, dimensions)
84+
85+
86+
def assert_empty_folder(path: Path):
87+
assert [p.name for p in path.iterdir()] == []

0 commit comments

Comments
 (0)