daml/rules_daml/daml.bzl
Moritz Kiefer 4fd1cf7c28
Fix doctests (#9225)
Haven’t tracked down when they broke. They used to work at some point
but atm they are very broken and don’t test anything.

This PR fixes the bash mess to parse the files correctly and fixes two
minor issues that creeped in by virtue of the doctests being broken.

changelog_begin
changelog_end
2021-03-24 12:56:37 +01:00

416 lines
12 KiB
Python

# Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
load("@build_environment//:configuration.bzl", "ghc_version", "sdk_version")
load("//bazel_tools/sh:sh.bzl", "sh_inline_test")
_damlc = attr.label(
allow_single_file = True,
default = Label("//compiler/damlc:damlc-compile-only"),
executable = True,
cfg = "host",
doc = "The DAML compiler.",
)
_zipper = attr.label(
allow_single_file = True,
default = Label("@bazel_tools//tools/zip:zipper"),
executable = True,
cfg = "host",
)
ghc_opts = ["--ghc-option=-Werror"]
def _daml_configure_impl(ctx):
project_name = ctx.attr.project_name
project_version = ctx.attr.project_version
daml_yaml = ctx.outputs.daml_yaml
target = ctx.attr.target
opts = ghc_opts + ["--target={}".format(target)] if target else ghc_opts
ctx.actions.write(
output = daml_yaml,
content = """
sdk-version: {sdk}
name: {name}
version: {version}
source: .
dependencies: []
build-options: [{opts} ]
""".format(
sdk = sdk_version,
name = project_name,
version = project_version,
opts = ", ".join(opts),
),
)
_daml_configure = rule(
implementation = _daml_configure_impl,
attrs = {
"project_name": attr.string(
mandatory = True,
doc = "Name of the DAML project.",
),
"project_version": attr.string(
mandatory = True,
doc = "Version of the DAML project.",
),
"daml_yaml": attr.output(
mandatory = True,
doc = "The generated daml.yaml config file.",
),
"target": attr.string(
doc = "DAML-LF version to output.",
),
},
)
def file_of_target(k):
[file] = k.files.to_list()
return file
def make_cp_command(src, dest):
return "mkdir -p $(dirname {dest}); cp -f {src} {dest}".format(
src = src,
dest = dest,
)
def _daml_build_impl(ctx):
name = ctx.label.name
daml_yaml = ctx.file.daml_yaml
srcs = ctx.files.srcs
dar_dict = ctx.attr.dar_dict
damlc = ctx.file._damlc
input_dars = [file_of_target(k) for k in dar_dict.keys()]
output_dar = ctx.outputs.dar
posix = ctx.toolchains["@rules_sh//sh/posix:toolchain_type"]
ctx.actions.run_shell(
tools = [damlc],
inputs = [daml_yaml] + srcs + input_dars,
outputs = [output_dar],
progress_message = "Building DAML project %s" % name,
command = """
set -eou pipefail
tmpdir=$(mktemp -d)
trap "rm -rf $tmpdir" EXIT
cp -f {config} $tmpdir/daml.yaml
# Having to produce all the daml.yaml files via a genrule is annoying
# so we allow hardcoded version numbers and patch them here.
{sed} -i 's/^sdk-version:.*$/sdk-version: {sdk_version}/' $tmpdir/daml.yaml
{cp_srcs}
{cp_dars}
{damlc} build --project-root $tmpdir {ghc_opts} -o $PWD/{output_dar}
""".format(
config = daml_yaml.path,
cp_srcs = "\n".join([
make_cp_command(
src = src.path,
dest = "$tmpdir/" + src.path,
)
for src in srcs
]),
cp_dars = "\n".join([
make_cp_command(
src = file_of_target(k).path,
dest = "$tmpdir/" + v,
)
for k, v in dar_dict.items()
]),
sed = posix.commands["sed"],
damlc = damlc.path,
output_dar = output_dar.path,
sdk_version = sdk_version,
ghc_opts = " ".join(ghc_opts),
),
)
_daml_build = rule(
implementation = _daml_build_impl,
attrs = {
"daml_yaml": attr.label(
allow_single_file = True,
mandatory = True,
doc = "The daml.yaml config file.",
),
"srcs": attr.label_list(
allow_files = [".daml"],
mandatory = True,
doc = "DAML files in this DAML project.",
),
"dar_dict": attr.label_keyed_string_dict(
mandatory = True,
allow_files = True,
doc = "Other DAML projects referenced by this DAML project.",
),
"dar": attr.output(
mandatory = True,
doc = "The generated DAR file.",
),
"_damlc": _damlc,
},
toolchains = ["@rules_sh//sh/posix:toolchain_type"],
)
def _extract_main_dalf_impl(ctx):
project_name = ctx.attr.project_name
project_version = ctx.attr.project_version
input_dar = ctx.file.dar
output_dalf = ctx.outputs.dalf
zipper = ctx.file._zipper
posix = ctx.toolchains["@rules_sh//sh/posix:toolchain_type"]
ctx.actions.run_shell(
tools = [zipper],
inputs = [input_dar],
outputs = [output_dalf],
progress_message = "Extract DALF from DAR (%s)" % project_name,
command = """
set -eoux pipefail
TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT
# While zipper has a -d option, it insists on it
# being a relative path so we don't use it.
ZIPPER=$PWD/{zipper}
DAR=$PWD/{input_dar}
(cd $TMPDIR && $ZIPPER x $DAR)
main_dalf=$({find} $TMPDIR/ -name '{project_name}-{project_version}-[a-z0-9]*.dalf')
cp $main_dalf {output_dalf}
""".format(
zipper = zipper.path,
find = posix.commands["find"],
project_name = project_name,
project_version = project_version,
input_dar = input_dar.path,
output_dalf = output_dalf.path,
),
)
_extract_main_dalf = rule(
implementation = _extract_main_dalf_impl,
attrs = {
"project_name": attr.string(
mandatory = True,
doc = "Name of the DAML project.",
),
"project_version": attr.string(
mandatory = True,
doc = "Version of the DAML project.",
),
"dar": attr.label(
allow_single_file = [".dar"],
mandatory = True,
doc = "The DAR from which the DALF will be extracted.",
),
"dalf": attr.output(
mandatory = True,
doc = "The extracted DALF.",
),
"_zipper": _zipper,
},
toolchains = ["@rules_sh//sh/posix:toolchain_type"],
)
def _daml_validate_test(
name,
dar,
**kwargs):
damlc = "//compiler/damlc:damlc-compile-only"
sh_inline_test(
name = name,
data = [damlc, dar],
cmd = """\
DAMLC=$$(canonicalize_rlocation $(rootpath {damlc}))
$$DAMLC validate-dar $$(canonicalize_rlocation $(rootpath {dar}))
""".format(
damlc = damlc,
dar = dar,
),
**kwargs
)
def _inspect_dar(base):
name = base + "-inspect"
dar = base + ".dar"
pp = base + ".dar.pp"
native.genrule(
name = name,
srcs = [
dar,
"//compiler/damlc:damlc-compile-only",
],
outs = [pp],
cmd = "$(location //compiler/damlc:damlc-compile-only) inspect $(location :" + dar + ") > $@",
)
_default_project_version = "1.0.0"
def daml_compile(
name,
srcs,
version = _default_project_version,
target = None,
project_name = None,
**kwargs):
"Build a DAML project, with a generated daml.yaml."
if len(srcs) == 0:
fail("daml_compile: Expected `srcs' to be non-empty.")
daml_yaml = name + ".yaml"
_daml_configure(
name = name + ".configure",
project_name = project_name or name,
project_version = version,
daml_yaml = daml_yaml,
target = target,
**kwargs
)
_daml_build(
name = name + ".build",
daml_yaml = daml_yaml,
srcs = srcs,
dar_dict = {},
dar = name + ".dar",
**kwargs
)
_inspect_dar(
base = name,
)
def daml_compile_with_dalf(
name,
version = _default_project_version,
**kwargs):
"Build a DAML project, with a generated daml.yaml, and extract the main DALF."
daml_compile(
name = name,
version = version,
**kwargs
)
_extract_main_dalf(
name = name + ".extract",
project_name = name,
project_version = version,
dar = name + ".dar",
dalf = name + ".dalf",
)
def daml_build_test(
name,
project_dir,
daml_config_basename = "daml.yaml",
daml_subdir_basename = "daml",
daml_yaml = None,
dar_dict = {},
**kwargs):
"Build a DAML project and validate the resulting .dar file."
if not daml_yaml:
daml_yaml = project_dir + "/" + daml_config_basename
srcs = native.glob([project_dir + "/" + daml_subdir_basename + "/**/*.daml"])
_daml_build(
name = name,
daml_yaml = daml_yaml,
srcs = srcs,
dar_dict = dar_dict,
dar = name + ".dar",
**kwargs
)
_daml_validate_test(
name = name + ".test",
dar = name + ".dar",
)
def daml_test(
name,
srcs = [],
deps = [],
data_deps = [],
damlc = "//compiler/damlc:damlc",
target = None,
**kwargs):
sh_inline_test(
name = name,
data = [damlc] + srcs + deps + data_deps,
cmd = """\
set -eoux pipefail
tmpdir=$$(mktemp -d)
trap "rm -rf $$tmpdir" EXIT
DAMLC=$$(canonicalize_rlocation $(rootpath {damlc}))
rlocations () {{ for i in $$@; do echo $$(canonicalize_rlocation $$i); done; }}
DEPS=($$(rlocations {deps}))
DATA_DEPS=($$(rlocations {data_deps}))
JOINED_DATA_DEPS="$$(printf ',"%s"' $${{DATA_DEPS[@]}})"
echo "$$JOINED_DATA_DEPS"
cat << EOF > $$tmpdir/daml.yaml
build-options: [{target}]
sdk-version: {sdk_version}
name: test
version: 0.0.1
source: .
dependencies: [daml-stdlib, daml-prim $$([ $${{#DEPS[@]}} -gt 0 ] && printf ',"%s"' $${{DEPS[@]}})]
data-dependencies: [$$([ $${{#DATA_DEPS[@]}} -gt 0 ] && printf '%s' $${{JOINED_DATA_DEPS:1}})]
EOF
cat $$tmpdir/daml.yaml
{cp_srcs}
cd $$tmpdir
$$DAMLC test --files {files}
""".format(
damlc = damlc,
files = " ".join(["$(rootpaths %s)" % src for src in srcs]),
sdk_version = sdk_version,
deps = " ".join(["$(rootpaths %s)" % dep for dep in deps]),
data_deps = " ".join(["$(rootpaths %s)" % dep for dep in data_deps]),
cp_srcs = "\n".join([
"mkdir -p $$(dirname {dest}); cp -f {src} {dest}".format(
src = "$$(canonicalize_rlocation $(rootpath {}))".format(src),
dest = "$$tmpdir/$(rootpath {})".format(src),
)
for src in srcs
]),
target = "--target=" + target if (target) else "",
),
**kwargs
)
def daml_doc_test(
name,
package_name,
srcs = [],
ignored_srcs = [],
flags = [],
cpp = "@stackage-exe//hpp",
damlc = "//compiler/damlc:damlc",
**kwargs):
sh_inline_test(
name = name,
data = [cpp, damlc] + srcs,
cmd = """\
set -eou pipefail
CPP=$$(canonicalize_rlocation $(rootpath {cpp}))
DAMLC=$$(canonicalize_rlocation $(rootpath {damlc}))
FILES=($$(
for file in {files}; do
IGNORED=false
for pattern in {ignored}; do
if [[ $$file = *$$pattern ]]; then
IGNORED=true
continue
fi
done
if [[ $$IGNORED == "false" ]]; then
echo $$(canonicalize_rlocation $$file)
fi
done
))
$$DAMLC doctest {flags} --cpp $$CPP --package-name {package_name}-{version} "$${{FILES[@]}}"
""".format(
cpp = cpp,
damlc = damlc,
package_name = package_name,
flags = " ".join(flags),
version = ghc_version,
files = " ".join(["$(rootpaths %s)" % src for src in srcs]),
ignored = " ".join(ignored_srcs),
),
**kwargs
)