Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,6 @@ haskell_library(

haskell_test(
name = "example-test",
srcs = {"Main.hs": "test/Main.hs"},
srcs = { "Main" : "test/Main.hs" },
deps = [":example"],
)
48 changes: 7 additions & 41 deletions haskell.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

load("//buck2:alex_happy.bzl", "alex", "happy")
load("//buck2:hsc2hs.bzl", "hsc2hs")
load("@prelude//haskell/util.bzl", "src_to_module_name")
load("@prelude//paths.bzl", "paths")

# Packages implicitly needed by every Haskell target.
AUTO_PACKAGES = ["base", "rts"]
Expand Down Expand Up @@ -206,15 +208,8 @@ def hs_module_path(path):
return path[:-len(ext)] + ".hs"
return path

def _resolve_src(name, path, src, deps, hsc_flags):
# `path` is the module-derived path this source should end up at (e.g.
# what its module name maps to); `src` is the actual file, which may
# differ from `path` for a source living outside its module's directory
# layout (see the dict form of `srcs`, below). Whatever we return here
# always ends up in a plain *list* passed to the native rule - srcs as a
# dict is deprecated - so every branch must produce an artifact whose
# own path already matches `path`.
out = hs_module_path(path)
def _resolve_src(name, src, deps, hsc_flags):
out = paths.replace_extension(src, ".hs")
if src.endswith(".hsc"):
rule_name = name + "-hsc-" + out.replace("/", "_")
hsc2hs(name = rule_name, hsc_file = src, out = out, deps = deps, extra_flags = hsc_flags)
Expand All @@ -227,41 +222,12 @@ def _resolve_src(name, path, src, deps, hsc_flags):
rule_name = name + "-happy-" + out.replace("/", "_")
happy(name = rule_name, src = src, out = out)
return ":" + rule_name
elif path == src or src.endswith("[" + path + "]"):
# Already at the right path: either a real file living exactly
# there, or a sub-target reference (e.g. from thrift_compile() via
# thrift_library() in thrift.bzl) whose bracketed key already
# equals `path` - its own artifact's
# short_path is already correct, so relocating it again would just
# be a redundant copy.
return src
else:
# A real (already-.hs) source that doesn't live at its module path -
# relocate it with export_file() (the same ctx.actions.copy_file()
# primitive export_file.bzl itself uses), so the native rule always
# sees a correctly-pathed source. This is what makes a module
# registered correctly in the package db even when nothing in this
# target imports it directly (i.e. it's only consumed by a *different*
# target depending on this one) - confirmed empirically: a plain
# (non-relocated) src with the wrong derived path still compiles
# within its own target (GHC resolves same-target imports from the
# sources' own `module X where` headers, not buck2's bookkeeping),
# but a cross-target `import` of it fails, since haskell_library()
# registers the *derived* path as the exposed module name.
rule_name = name + "-mv-" + path.replace("/", "_")
native.export_file(name = rule_name, src = src, out = path)
return ":" + rule_name
return src

# `srcs` is usually a list, where each file's own path (relative to this
# BUCK package) determines its module name. A dict `{modulePath: file}` is
# also accepted for the rare case where a source doesn't live at the path
# its module name implies (e.g. a shared `plugins/` directory holding
# modules that belong under the main package's namespace) - internally
# resolved to a plain list (see _resolve_src) since dict-form srcs on the
# native rule is deprecated.
def _resolve_srcs(name, srcs, deps, hsc_flags):
items = srcs.items() if type(srcs) == type({}) else [(src, src) for src in srcs]
return [_resolve_src(name, path, src, deps, hsc_flags) for path, src in items]
items = srcs.items() if type(srcs) == type({}) else [(src_to_module_name(src), src) for src in srcs]
return { modl: _resolve_src(name, src, deps, hsc_flags) for modl, src in items }

def haskell_library(
name,
Expand Down
7 changes: 6 additions & 1 deletion prelude/decls/haskell_common.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ def _srcs_arg():
sorted = True,
default = [],
doc = """
A list of Haskell sources to be built by this rule. The dictionary option is deprecated.
A list of Haskell sources to be built by this rule. In the list form,
each source file must match the module name with "." replaced by "/", e.g.
`[ "Main.hs", "Other/Module.hs" ]`.
If the source files don't match the module names 1:1, then you
can use the dict form, in which the keys are module names, e.g.
`{ "Main": "src/test.hs", "Other.Module" : "src/Other/Module.hs" }`
""",
),
}
Expand Down
4 changes: 2 additions & 2 deletions prelude/haskell/compile.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,10 @@ def compile_args(ctx: AnalysisContext, link_style: LinkStyle, enable_profiling:

arg_srcs = []
hidden_srcs = []
for path, src in srcs_to_pairs(ctx.attrs.srcs):
for _, src in srcs_to_pairs(ctx.attrs.srcs):
# hs-boot files aren't expected to be an argument to compiler but does need
# to be included in the directory of the associated src file
if is_haskell_src(path):
if is_haskell_src(src):
arg_srcs.append(src)
else:
hidden_srcs.append(src)
Expand Down
7 changes: 4 additions & 3 deletions prelude/haskell/haskell.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ load(
"is_haskell_src",
"output_extensions",
"src_to_module_name",
"module_name_to_file",
"srcs_to_pairs",
)
load(
Expand Down Expand Up @@ -369,10 +370,10 @@ def haskell_prebuilt_library_impl(ctx: AnalysisContext) -> list[Provider]:

def _srcs_to_objfiles(ctx: AnalysisContext, odir: Artifact, osuf: str) -> list[Artifact]:
objfiles = []
for src, _ in srcs_to_pairs(ctx.attrs.srcs):
for modl, src in srcs_to_pairs(ctx.attrs.srcs):
# Don't link boot sources, as they're only meant to be used for compiling.
if is_haskell_src(src):
objfiles.append(odir.project(paths.replace_extension(src, "." + osuf)))
objfiles.append(odir.project(module_name_to_file(modl, osuf)))
return objfiles

_REGISTER_PACKAGE = """\
Expand Down Expand Up @@ -437,7 +438,7 @@ def _make_package(
hi_link_style = hi_link_style if hi_link_style != None else link_style

# Don't expose boot sources, as they're only meant to be used for compiling.
modules = [src_to_module_name(x) for x, _ in srcs_to_pairs(ctx.attrs.srcs) if is_haskell_src(x)]
modules = [x for x, src in srcs_to_pairs(ctx.attrs.srcs) if is_haskell_src(src)]

if enable_profiling:
# Add the `-p` suffix otherwise ghc will look for objects
Expand Down
9 changes: 6 additions & 3 deletions prelude/haskell/util.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,19 @@ def srcs_to_pairs(srcs) -> list[(str, Artifact)]:
if type(srcs) == type({}):
return srcs.items()
else:
return [(src.short_path, src) for src in srcs]
return [(src_to_module_name(src.short_path), src) for src in srcs]

def is_haskell_src(x: str) -> bool:
_, ext = paths.split_extension(x)
def is_haskell_src(x: Artifact) -> bool:
_, ext = paths.split_extension(x.short_path)
return ext in HASKELL_EXTENSIONS

def src_to_module_name(x: str) -> str:
base, _ext = paths.split_extension(x)
return base.replace("/", ".")

def module_name_to_file(x: str, ext: str) -> str:
return x.replace(".", "/") + "." + ext

def attr_deps(ctx: AnalysisContext) -> list[Dependency]:
return ctx.attrs.deps

Expand Down
Loading