daml/rules_daml/daml.bzl
nickchapman-da d81caa9568
rework daml_compile bazel rule (#5070)
Avoiding `damlc compile/package` commands (which we would like to deprecate), and replace with plain `damlc build` together with a post dar->dalf extraction step in the couple of places where we actually want the .dalf for testing.

changelog_begin
changelog_end
2020-03-18 23:06:55 +00:00

404 lines
12 KiB
Python

# Copyright (c) 2020 The DAML Authors. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
load("@build_environment//:configuration.bzl", "ghc_version", "sdk_version")
_damlc = attr.label(
allow_single_file = True,
default = Label("//compiler/damlc"),
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",
)
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
ctx.actions.write(
output = daml_yaml,
content = """
sdk-version: {sdk}
name: {name}
version: {version}
source: .
dependencies: []
build-options: [{target}]
""".format(
sdk = sdk_version,
name = project_name,
version = project_version,
target = "--target=" + target if (target) else "",
),
)
_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
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
{cp_srcs}
{cp_dars}
{damlc} build --project-root $tmpdir -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()
]),
damlc = damlc.path,
output_dar = output_dar.path,
),
)
_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,
doc = "Other DAML projects referenced by this DAML project.",
),
"dar": attr.output(
mandatory = True,
doc = "The generated DAR file.",
),
"_damlc": _damlc,
},
)
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 -eou pipefail
{zipper} x {input_dar}
main_dalf=$({find} . -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_impl(ctx):
name = ctx.label.name
dar = ctx.file.dar
script = ctx.actions.declare_file(name + ".sh")
damlc = ctx.file._damlc
script_content = """
set -eou pipefail
DAMLC=$(rlocation $TEST_WORKSPACE/{damlc})
DAR=$(rlocation $TEST_WORKSPACE/{dar})
$DAMLC validate-dar $DAR
""".format(
damlc = damlc.short_path,
dar = dar.short_path,
)
ctx.actions.write(
output = script,
content = script_content,
)
runfiles = ctx.runfiles(files = [dar, damlc])
return [DefaultInfo(executable = script, runfiles = runfiles)]
_daml_validate_test = rule(
implementation = _daml_validate_test_impl,
attrs = {
"dar": attr.label(
allow_single_file = True,
mandatory = True,
doc = "The DAR to validate.",
),
"_damlc": _damlc,
},
test = True,
)
_default_project_version = "1.0.0"
def daml_compile(
name,
srcs,
version = _default_project_version,
target = 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 = 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
)
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",
dar_dict = {},
**kwargs):
"Build a DAML project and validate the resulting .dar file."
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_impl(ctx):
script = """
set -eou pipefail
DAMLC=$(rlocation $TEST_WORKSPACE/{damlc})
rlocations () {{ for i in $@; do echo $(rlocation $TEST_WORKSPACE/$i); done; }}
$DAMLC test --files $(rlocations "{files}")
""".format(
damlc = ctx.executable.damlc.short_path,
files = " ".join([f.short_path for f in ctx.files.srcs]),
)
ctx.actions.write(
output = ctx.outputs.executable,
content = script,
)
damlc_runfiles = ctx.attr.damlc[DefaultInfo].data_runfiles
runfiles = ctx.runfiles(
collect_data = True,
files = ctx.files.srcs,
).merge(damlc_runfiles)
return [DefaultInfo(runfiles = runfiles)]
daml_test = rule(
implementation = _daml_test_impl,
attrs = {
"srcs": attr.label_list(
allow_files = [".daml"],
default = [],
doc = "DAML source files to test.",
),
"damlc": attr.label(
executable = True,
cfg = "host",
allow_files = True,
default = Label("//compiler/damlc"),
),
},
test = True,
)
def _daml_doctest_impl(ctx):
script = """
set -eou pipefail
DAMLC=$(rlocation $TEST_WORKSPACE/{damlc})
CPP=$(rlocation $TEST_WORKSPACE/{cpp})
rlocations () {{ for i in $@; do echo $(rlocation $TEST_WORKSPACE/$i); done; }}
$DAMLC doctest {flags} --cpp $CPP --package-name {package_name}-{version} $(rlocations "{files}")
""".format(
damlc = ctx.executable.damlc.short_path,
# we end up with "../hpp/hpp" while we want "external/hpp/hpp"
# so we just do the replacement ourselves.
cpp = ctx.executable.cpp.short_path.replace("..", "external"),
package_name = ctx.attr.package_name,
flags = " ".join(ctx.attr.flags),
version = ghc_version,
files = " ".join([
f.short_path
for f in ctx.files.srcs
if all([not f.short_path.endswith(ignore) for ignore in ctx.attr.ignored_srcs])
]),
)
ctx.actions.write(
output = ctx.outputs.executable,
content = script,
)
damlc_runfiles = ctx.attr.damlc[DefaultInfo].data_runfiles
cpp_runfiles = ctx.attr.cpp[DefaultInfo].data_runfiles
runfiles = ctx.runfiles(
collect_data = True,
files = ctx.files.srcs,
).merge(damlc_runfiles).merge(cpp_runfiles)
return [DefaultInfo(runfiles = runfiles)]
daml_doc_test = rule(
implementation = _daml_doctest_impl,
attrs = {
"srcs": attr.label_list(
allow_files = [".daml"],
default = [],
doc = "DAML source files that should be tested.",
),
"ignored_srcs": attr.string_list(
default = [],
doc = "DAML source files that should be ignored.",
),
"damlc": attr.label(
executable = True,
cfg = "host",
allow_files = True,
default = Label("//compiler/damlc"),
),
"cpp": attr.label(
executable = True,
cfg = "host",
allow_files = True,
default = Label("@hpp//:hpp"),
),
"flags": attr.string_list(
default = [],
doc = "Flags for damlc invokation.",
),
"package_name": attr.string(),
},
test = True,
)