Skip to content

Commit ef28382

Browse files
committed
Merge branch 'main' into mlin-wdl2-runtime
2 parents 8669111 + 7f3068f commit ef28382

File tree

16 files changed

+1106
-32
lines changed

16 files changed

+1106
-32
lines changed

.github/copilot-instructions.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
You're working on the source code for `miniwdl`, the Workflow Description Language (WDL) runner and developer toolkit.
2+
3+
See CONTRIBUTING.md for an overview of the codebase and development workflow. Note in particular that Python code should be linted with `mypy`, `ruff check`, and `ruff format`.
4+
5+
For many tasks it'll be useful to refer to the WDL 1.2 specification, which you can find under spec/wdl-1.2/SPEC.md. The version changelog is spec/wdl-1.2/CHANGELOG.md, and the older version 1.1 spec is spec/wdl-1.1/SPEC.md.
6+
7+
These development tutorials under docs/ introduce a few common ways the codebase is used and extended.
8+
- trace_identifiers.md -- basic syntax tree traversal
9+
- wdlviz.md -- generating graphviz diagrams from WDL source code
10+
- add_functions.md -- adding new functions to the standard library
11+
- assert.md -- adding a new WDL language feature, with parsing, type-checking, and runtime execution

.gitmodules

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,14 @@
6161
[submodule "test_corpi/broadinstitute/warp"]
6262
path = test_corpi/broadinstitute/warp
6363
url = https://github.com/broadinstitute/warp.git
64+
[submodule "wdl-1.2"]
65+
path = spec/wdl-1.2
66+
url = https://github.com/openwdl/wdl.git
67+
branch = wdl-1.2
68+
[submodule "wdl-1.1"]
69+
path = spec/wdl-1.1
70+
url = https://github.com/openwdl/wdl.git
71+
branch = wdl-1.1
72+
[submodule "spec/wdl-tests"]
73+
path = spec/wdl-tests
74+
url = https://github.com/openwdl/wdl-tests.git

CONTRIBUTING.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Generally, `python3 -m WDL ...` invokes the equivalent of the `miniwdl ...` entr
2121
The Makefile has a few typical flows:
2222

2323
- `make` or `make test` runs the full test suite with code coverage report (takes several minutes)
24-
- `make qtest` runs most of the tests more quickly (by omitting some slower cases, and not tracking coverage)
24+
- `make qtest` runs most of the tests more quickly (by omitting some slower cases, not tracking coverage, and failing fast)
2525
- `make pretty` reformats the code with `ruff format`
2626
- `make check` validates the code with `ruff check` and `mypy`
2727

@@ -117,5 +117,6 @@ The miniwdl test suite is located in the `tests/` directory and is organized int
117117
- true unit tests of isolated components
118118
- artificial WDL sources exercising various aspects of the runtime
119119
- tests of the parser and linter on a collection of WDL from external sources found under `test_corpi/`
120+
* **WDL Spec Examples (spec_tests/):** Tests all the examples embedded in the WDL SPEC.md (extracted following the [WDL Markdown Test Specification](https://github.com/openwdl/wdl-tests/blob/main/docs/MarkdownTests.md)). The `config.yaml` file marks several examples xfail or skip, for various reasons commented there.
120121
* **Bash-TAP Tests (\*.t):** These files are shell scripts that use the `bash-tap` framework to run commands and check their output. They primarily exercise the `miniwdl` command-line interface (parsing arguments, executing workflows/tasks, etc.) and associated shell integration.
121122
* **Applied Tests (applied/):** These are bash-tap that run `miniwdl run` on complete WDL workflows, drawn from various sources, with specific input JSON files. These tests are used to ensure that miniwdl correctly executes realistic workflows.

Dockerfile

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
# or append 'bash' to that to enter interactive shell
1010

1111
# start with ubuntu:20.04 plus some apt packages
12-
FROM ubuntu:20.04 as deps
13-
ENV LC_ALL C.UTF-8
14-
ENV LANG C.UTF-8
12+
FROM ubuntu:20.04 AS deps
13+
ENV LC_ALL=C.UTF-8
14+
ENV LANG=C.UTF-8
1515
RUN apt-get -qq update && DEBIAN_FRONTEND=noninteractive apt-get -qq install -y \
1616
python3-pip python3-setuptools tzdata wget zip git-core default-jre jq shellcheck docker.io
1717
RUN pip3 install yq
@@ -25,10 +25,11 @@ RUN mkdir miniwdl
2525
# https://github.com/actions/checkout/issues/760
2626
RUN git config --global --add safe.directory /home/wdler/miniwdl
2727

28-
ENV PATH $PATH:/home/wdler/.local/bin
28+
ENV PATH=$PATH:/home/wdler/.local/bin
29+
ENV PYTHONPATH=/home/wdler/miniwdl:$PYTHONPATH
2930
COPY pyproject.toml /home/wdler/miniwdl
3031
RUN tomlq -r '(.project.dependencies + .project["optional-dependencies"].dev)[]' miniwdl/pyproject.toml \
3132
| xargs pip3 install --user && rm miniwdl/pyproject.toml
3233

3334
# expectation -- mount miniwdl source tree at /home/wdler/miniwdl
34-
CMD make -C miniwdl unit_tests
35+
CMD ["make","-C","miniwdl","unit_tests"]

Makefile

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ test: check_check check unit_tests integration_tests
66
# fail fast
77
qtest:
88
python3 tests/no_docker_services.py
9-
pytest -vx --tb=short -n `python3 -c 'import multiprocessing as mp; print(mp.cpu_count())'` --dist=loadscope tests
9+
pytest -vx --tb=short -n auto --dist=loadscope tests
10+
pytest -vx --tb=short -n auto tests/spec_tests/spec_tests.py
1011
python3 -m unittest tests.test_cli_argcomplete
1112
prove -v tests/{check,eval,runner,zip}.t
1213
python3 tests/no_docker_services.py
1314

1415
unit_tests:
15-
pytest -v --tb=short -n `python3 -c 'import multiprocessing as mp; print(mp.cpu_count())'` --dist=loadscope --cov=WDL tests
16+
pytest -v --tb=short -n auto --dist=loadscope --cov=WDL tests
17+
pytest -v --tb=short -n auto tests/spec_tests/spec_tests.py
1618
python3 -m unittest tests.test_cli_argcomplete
17-
python3 tests/no_docker_services.py
19+
python3 tests/no_docker_services.py
1820

1921
integration_tests:
2022
prove -v tests/{check,eval,runner,zip,multi_line_strings}.t
@@ -79,4 +81,10 @@ doc:
7981

8082
docs: doc
8183

82-
.PHONY: check check_check pretty test qtest docker doc docs pypi_test pypi bdist ci_housekeeping unit_tests integration_tests skylab_bulk_rna DVGLx viral_assemble
84+
.PHONY: \
85+
pretty check check_check \
86+
test qtest unit_tests integration_tests \
87+
skylab_bulk_rna DVGLx viral_assemble viral_refbased singularity_tests \
88+
doc docs \
89+
bdist pypi_test pypi \
90+
ci_housekeeping ci_unit_tests

WDL/StdLib.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -653,8 +653,7 @@ def _call_eager(self, expr: "Expr.Apply", arguments: List[Value.Base]) -> Value.
653653

654654

655655
class _ComparisonOperator(EagerFunction):
656-
# < > <= >= operators; reuses some of the equatability logic, but not valid for optional or
657-
# compound types.
656+
# < > <= >= operators
658657

659658
name: str
660659
op: Callable
@@ -665,7 +664,9 @@ def __init__(self, name: str, op: Callable) -> None:
665664

666665
def infer_type(self, expr: "Expr.Apply") -> Type.Base:
667666
assert len(expr.arguments) == 2
668-
if not expr.arguments[0].type.comparable(expr.arguments[1].type):
667+
if not expr.arguments[0].type.comparable(
668+
expr.arguments[1].type, check_quant=expr._check_quant
669+
):
669670
raise Error.IncompatibleOperand(
670671
expr,
671672
"Cannot compare {} and {}".format(

WDL/Type.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -132,26 +132,29 @@ def __str__(self) -> str:
132132
def __eq__(self, rhs: typing.Any) -> bool:
133133
return isinstance(rhs, Base) and str(self) == str(rhs)
134134

135-
def equatable(self, rhs: "Base", compound: bool = False) -> bool:
135+
def equatable(self, rhs: "Base", *, compound: bool = False) -> bool:
136136
"""
137137
Check if values of the given types may be equated with the WDL == operator (and its
138138
negation). This mostly requires they have the same type, with quirks like ignoring
139139
quantifiers and Int/Float coercion (allowed at 'top level' but not within compound types).
140140
"""
141141
return isinstance(rhs, (type(self), Any)) # intentionally ignores optional quantifier
142142

143-
def comparable(self, rhs: "Base") -> bool:
143+
def comparable(self, rhs: "Base", *, check_quant: bool = True) -> bool:
144144
"""
145145
Check if values of the given types may be compared with the WDL comparison operators (<, <=,
146-
>, >=). Currently these are allowed only for non-optional primitive values.
146+
>, >=).
147147
"""
148+
# Since this is currently only allowed for primitive types, we can code all cases here
149+
# instead of fanning out to subclasses (still possible if we need to in the future).
148150
allowed = (Int, Float, String, Boolean)
149-
return (
150-
isinstance(self, allowed)
151-
and not self.optional
152-
and isinstance(rhs, allowed)
153-
and not rhs.optional
154-
)
151+
if not (isinstance(self, allowed) and isinstance(rhs, allowed)):
152+
return False
153+
if check_quant and (self.optional or rhs.optional):
154+
return False
155+
if isinstance(self, (Int, Float)) and isinstance(rhs, (Int, Float)):
156+
return True
157+
return isinstance(rhs, type(self))
155158

156159

157160
class Any(Base):
@@ -172,7 +175,7 @@ def check(self, rhs: Base, check_quant: bool = True) -> None:
172175
def __str__(self) -> str:
173176
return "None" if self._optional else "Any"
174177

175-
def equatable(self, rhs, compound: bool = False):
178+
def equatable(self, rhs, *, compound: bool = False):
176179
return True
177180

178181

@@ -197,9 +200,9 @@ def check(self, rhs: Base, check_quant: bool = True) -> None:
197200
return self._check_optional(rhs, check_quant)
198201
super().check(rhs, check_quant)
199202

200-
def equatable(self, rhs, compound: bool = False):
203+
def equatable(self, rhs, *, compound: bool = False):
201204
# per WDL spec, Int/Float can be equated directly, but not as part of a compound type
202-
return super().equatable(rhs, compound) or (not compound and isinstance(rhs, Int))
205+
return super().equatable(rhs, compound=compound) or (not compound and isinstance(rhs, Int))
203206

204207

205208
class Int(Base):
@@ -214,8 +217,10 @@ def check(self, rhs: Base, check_quant: bool = True) -> None:
214217
return self._check_optional(rhs, check_quant)
215218
super().check(rhs, check_quant)
216219

217-
def equatable(self, rhs, compound: bool = False):
218-
return super().equatable(rhs, compound) or (not compound and isinstance(rhs, Float))
220+
def equatable(self, rhs, *, compound: bool = False):
221+
return super().equatable(rhs, compound=compound) or (
222+
not compound and isinstance(rhs, Float)
223+
)
219224

220225

221226
class File(Base):
@@ -311,7 +316,7 @@ def copy(self, optional: Optional[bool] = None, nonempty: Optional[bool] = None)
311316
setattr(ans, "_nonempty", nonempty)
312317
return ans
313318

314-
def equatable(self, rhs, compound: bool = False):
319+
def equatable(self, rhs, *, compound: bool = False):
315320
# intentionally ignores optional and nonempty quantifiers:
316321
return isinstance(rhs, Array) and self.item_type.equatable(rhs.item_type, compound=True)
317322

@@ -388,7 +393,7 @@ def check(self, rhs: Base, check_quant: bool = True) -> None:
388393
return
389394
super().check(rhs, check_quant)
390395

391-
def equatable(self, rhs, compound: bool = False):
396+
def equatable(self, rhs, *, compound: bool = False):
392397
return (
393398
isinstance(rhs, Map)
394399
and self.item_type[0].equatable(rhs.item_type[0], compound=True)
@@ -436,7 +441,7 @@ def check(self, rhs: Base, check_quant: bool = True) -> None:
436441
return self._check_optional(rhs, check_quant)
437442
super().check(rhs, check_quant)
438443

439-
def equatable(self, rhs, compound=True):
444+
def equatable(self, rhs, *, compound: bool = True):
440445
return (
441446
isinstance(rhs, Pair)
442447
and self.left_type.equatable(rhs.left_type, compound=True)
@@ -501,7 +506,7 @@ def parameters(self) -> Iterable[Base]:
501506
assert self.members is not None
502507
return self.members.values()
503508

504-
def equatable(self, rhs, coerce=True):
509+
def equatable(self, rhs, *, compound: bool = False):
505510
return isinstance(rhs, StructInstance) and self.type_id == rhs.type_id
506511

507512

@@ -558,7 +563,7 @@ def check(self, rhs: Base, check_quant: bool = True) -> None:
558563
return
559564
raise TypeError()
560565

561-
def equatable(self, rhs, compound: bool = False):
566+
def equatable(self, rhs, *, compound: bool = False):
562567
return False
563568

564569

spec/wdl-1.1

Submodule wdl-1.1 added at d5fb794

spec/wdl-1.2

Submodule wdl-1.2 added at 91b6017

spec/wdl-tests

Submodule wdl-tests added at 58ff362

0 commit comments

Comments
 (0)