Skip to content

Commit c7f5274

Browse files
committed
create lockfile class
1 parent aa158ea commit c7f5274

3 files changed

Lines changed: 108 additions & 88 deletions

File tree

pycyphal/dsdl/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
from ._support_wrappers import to_builtin as to_builtin
3939
from ._support_wrappers import update_from_builtin as update_from_builtin
4040

41+
from ._lockfile import Lockfile
42+
4143

4244
def generate_package(*args, **kwargs): # type: ignore # pragma: no cover
4345
"""Deprecated alias of :func:`compile`."""

pycyphal/dsdl/_compiler.py

Lines changed: 70 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import nunavut
1616
import nunavut.lang
1717
import nunavut.jinja
18-
18+
from ._lockfile import Lockfile
1919

2020
_AnyPath = Union[str, pathlib.Path]
2121

@@ -149,101 +149,83 @@ def compile( # pylint: disable=redefined-builtin
149149
composite_types: list[pydsdl.CompositeType] = []
150150
output_directory = pathlib.Path(pathlib.Path.cwd() if output_directory is None else output_directory).resolve()
151151

152-
if root_namespace_directory is not None:
153-
root_namespace_directory = pathlib.Path(root_namespace_directory).resolve()
154-
if root_namespace_directory.parent == output_directory:
155-
# https://github.com/OpenCyphal/pycyphal/issues/133 and https://github.com/OpenCyphal/pycyphal/issues/127
156-
raise ValueError(
157-
"The specified destination may overwrite the DSDL root namespace directory. "
158-
"Consider specifying a different output directory instead."
152+
lockfile = Lockfile(root_namespace_directory, root_namespace_name, output_directory)
153+
lockfile_created = lockfile.create()
154+
155+
if lockfile_created:
156+
if root_namespace_directory is not None:
157+
root_namespace_directory = pathlib.Path(root_namespace_directory).resolve()
158+
if root_namespace_directory.parent == output_directory:
159+
# https://github.com/OpenCyphal/pycyphal/issues/133 and https://github.com/OpenCyphal/pycyphal/issues/127
160+
raise ValueError(
161+
"The specified destination may overwrite the DSDL root namespace directory. "
162+
"Consider specifying a different output directory instead."
163+
)
164+
165+
# Read the DSDL definitions
166+
composite_types = pydsdl.read_namespace(
167+
root_namespace_directory=str(root_namespace_directory),
168+
lookup_directories=list(map(str, lookup_directories or [])),
169+
allow_unregulated_fixed_port_id=allow_unregulated_fixed_port_id,
170+
)
171+
if not composite_types:
172+
_logger.info("Root namespace directory %r does not contain DSDL definitions", root_namespace_directory)
173+
return None
174+
(root_namespace_name,) = set(map(lambda x: x.root_namespace, composite_types)) # type: ignore
175+
_logger.info("Read %d definitions from root namespace %r", len(composite_types), root_namespace_name)
176+
177+
# Generate code
178+
assert isinstance(output_directory, pathlib.Path)
179+
root_ns = nunavut.build_namespace_tree(
180+
types=composite_types,
181+
root_namespace_dir=str(root_namespace_directory),
182+
output_dir=str(output_directory),
183+
language_context=language_context,
184+
)
185+
code_generator = nunavut.jinja.DSDLCodeGenerator(
186+
namespace=root_ns,
187+
generate_namespace_types=nunavut.YesNoDefault.YES,
188+
followlinks=True,
189+
)
190+
code_generator.generate_all()
191+
_logger.info(
192+
"Generated %d types from the root namespace %r in %.1f seconds",
193+
len(composite_types),
194+
root_namespace_name,
195+
time.monotonic() - started_at,
196+
)
197+
else:
198+
root_ns = nunavut.build_namespace_tree(
199+
types=[],
200+
root_namespace_dir=str(""),
201+
output_dir=str(output_directory),
202+
language_context=language_context,
159203
)
160204

161-
# Read the DSDL definitions
162-
composite_types = pydsdl.read_namespace(
163-
root_namespace_directory=str(root_namespace_directory),
164-
lookup_directories=list(map(str, lookup_directories or [])),
165-
allow_unregulated_fixed_port_id=allow_unregulated_fixed_port_id,
166-
)
167-
if not composite_types:
168-
_logger.info("Root namespace directory %r does not contain DSDL definitions", root_namespace_directory)
169-
return None
170-
(root_namespace_name,) = set(map(lambda x: x.root_namespace, composite_types)) # type: ignore
171-
_logger.info("Read %d definitions from root namespace %r", len(composite_types), root_namespace_name)
172-
173-
# Generate code
174-
assert isinstance(output_directory, pathlib.Path)
175-
root_ns = nunavut.build_namespace_tree(
176-
types=composite_types,
177-
root_namespace_dir=str(root_namespace_directory),
178-
output_dir=str(output_directory),
179-
language_context=language_context,
180-
)
181-
code_generator = nunavut.jinja.DSDLCodeGenerator(
205+
support_generator = nunavut.jinja.SupportGenerator(
182206
namespace=root_ns,
183-
generate_namespace_types=nunavut.YesNoDefault.YES,
184-
followlinks=True,
185-
)
186-
code_generator.generate_all()
187-
_logger.info(
188-
"Generated %d types from the root namespace %r in %.1f seconds",
189-
len(composite_types),
190-
root_namespace_name,
191-
time.monotonic() - started_at,
192-
)
193-
else:
194-
root_ns = nunavut.build_namespace_tree(
195-
types=[],
196-
root_namespace_dir=str(""),
197-
output_dir=str(output_directory),
198-
language_context=language_context,
199207
)
208+
support_generator.generate_all()
200209

201-
lockfile = f"{root_namespace_name}.lock"
202-
if not root_namespace_name:
203-
lockfile = "support.lock"
204-
lockfile_path = f"{output_directory}/{lockfile}"
205-
206-
while True:
207-
if not os.path.exists(lockfile_path):
208-
try:
209-
pathlib.Path(output_directory).mkdir(parents=True, exist_ok=True)
210-
pathlib.Path(lockfile_path).touch()
210+
# A minor UX improvement; see https://github.com/OpenCyphal/pycyphal/issues/115
211+
for p in sys.path:
212+
if pathlib.Path(p).resolve() == pathlib.Path(output_directory):
211213
break
212-
except PermissionError:
213-
_logger.warning("Unable to create lockfile at %s", lockfile_path)
214-
else:
215-
time.sleep(1)
216-
if pathlib.Path.exists(output_directory / pathlib.Path(root_namespace_name)):
217-
return GeneratedPackageInfo(
218-
path=pathlib.Path(output_directory) / pathlib.Path(root_namespace_name),
219-
models=composite_types,
220-
name=root_namespace_name,
221-
)
222-
223-
support_generator = nunavut.jinja.SupportGenerator(
224-
namespace=root_ns,
225-
)
226-
support_generator.generate_all()
227-
228-
# A minor UX improvement; see https://github.com/OpenCyphal/pycyphal/issues/115
229-
for p in sys.path:
230-
if pathlib.Path(p).resolve() == pathlib.Path(output_directory):
231-
break
232-
else:
233-
if os.name == "nt":
234-
quick_fix = f'Quick fix: `$env:PYTHONPATH += ";{output_directory.resolve()}"`'
235-
elif os.name == "posix":
236-
quick_fix = f'Quick fix: `export PYTHONPATH="{output_directory.resolve()}"`'
237214
else:
238-
quick_fix = "Quick fix is not available for this OS."
239-
_logger.info(
240-
"Generated package is stored in %r, which is not in Python module search path list. "
241-
"The package will fail to import unless you add the destination directory to sys.path or PYTHONPATH. %s",
242-
str(output_directory),
243-
quick_fix,
244-
)
215+
if os.name == "nt":
216+
quick_fix = f'Quick fix: `$env:PYTHONPATH += ";{output_directory.resolve()}"`'
217+
elif os.name == "posix":
218+
quick_fix = f'Quick fix: `export PYTHONPATH="{output_directory.resolve()}"`'
219+
else:
220+
quick_fix = "Quick fix is not available for this OS."
221+
_logger.info(
222+
"Generated package is stored in %r, which is not in Python module search path list. "
223+
"The package will fail to import unless you add the destination directory to sys.path or PYTHONPATH. %s",
224+
str(output_directory),
225+
quick_fix,
226+
)
245227

246-
pathlib.Path(lockfile_path).unlink()
228+
lockfile.remove()
247229

248230
return GeneratedPackageInfo(
249231
path=pathlib.Path(output_directory) / pathlib.Path(root_namespace_name),

pycyphal/dsdl/_lockfile.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import logging
2+
import pathlib
3+
import time
4+
5+
_logger = logging.getLogger(__name__)
6+
7+
8+
class Lockfile:
9+
10+
def __init__(self, root_namespace_directory, root_namespace_name, output_directory) -> None:
11+
self.output_directory = output_directory
12+
self.root_namespace_name = root_namespace_name
13+
self.root_namespace_directory = root_namespace_directory
14+
15+
@property
16+
def lockfile_path(self) -> str:
17+
return f"{self.output_directory}/{self.root_namespace_name if self.root_namespace_name else 'support'}.lock"
18+
19+
def create(self):
20+
root_namespace_name = self.root_namespace_name
21+
output_directory = self.output_directory
22+
lockfile_path = self.lockfile_path
23+
24+
while True:
25+
try:
26+
pathlib.Path(output_directory).mkdir(parents=True, exist_ok=True)
27+
fp = open(lockfile_path, "x")
28+
fp.close()
29+
return True
30+
except FileExistsError:
31+
time.sleep(1)
32+
if pathlib.Path.exists(output_directory / pathlib.Path(root_namespace_name)):
33+
return False
34+
35+
def remove(self):
36+
pathlib.Path(self.lockfile_path).unlink()

0 commit comments

Comments
 (0)