Skip to content
This repository has been archived by the owner on Jun 18, 2019. It is now read-only.

Commit

Permalink
Merge pull request #54 from DACH-NY/case-sensitive
Browse files Browse the repository at this point in the history
Avoid case-sensitive workspace names
  • Loading branch information
thumphries authored Jan 9, 2019
2 parents ecf380e + 38e9cef commit 561366d
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 56 deletions.
3 changes: 2 additions & 1 deletion BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ load("@io_tweag_rules_haskell//haskell:haskell.bzl",
"haskell_toolchain",
"haskell_doctest_toolchain",
)
load("//tools:mangling.bzl", "hazel_binary")

exports_files([
"hazel.bzl",
Expand All @@ -12,5 +13,5 @@ exports_files([

haskell_doctest_toolchain(
name = "doctest",
doctest = "@haskell_doctest//:doctest_bin",
doctest = hazel_binary("doctest"),
)
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,21 @@ dependency](https://docs.bazel.build/versions/master/external.html) for each
package. It downloads the corresponding Cabal tarball from Hackage
and construct build rules for compiling the components of that package.

Note, that Haskell package names are case-sensitive while Bazel workspace names
are case-insensitive on case-insensitive file systems. For the generated
workspace names to be case-insensitive we include a hash of the original
package name. Query for targets `@all_hazel_packages//:haskell_{package_name}`
to determine the generated workspace name.

For example:

```
# Discover the generated external workspace for the vector package.
bazel query 'deps(@all_hazel_packages//:haskell_vector, 1)'
# --> @haskell_vector__820387517//:bzl
# Build all components of a single package, as well as all of its dependencies:
bazel build @haskell_vector//:all
bazel build @haskell_vector__820387517//:all
# Build all components of all third-party packages:
bazel build @all_hazel_packages//:all
Expand Down
56 changes: 26 additions & 30 deletions hazel.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl",
load("@bazel_tools//tools/build_defs/repo:http.bzl",
"http_archive",
)
load("//tools:mangling.bzl", "hazel_binary", "hazel_library", "hazel_workspace")

def _cabal_haskell_repository_impl(ctx):
pkg = "{}-{}".format(ctx.attr.package_name, ctx.attr.package_version)
Expand Down Expand Up @@ -65,32 +66,33 @@ _core_library_repository = repository_rule(
})

def _all_hazel_packages_impl(ctx):
ctx.file("BUILD", """
all_packages_filegroup = """
filegroup(
name = "all-package-files",
srcs = [{}],
)
""".format(",".join(["\"{}\"".format(f) for f in ctx.attr.files])),
executable=False)
""".format(",".join(["\"@{}//:bzl\"".format(hazel_workspace(p)) for p in ctx.attr.packages]))
one_package_template = """
filegroup(
name = "haskell_{package_name}",
srcs = ["@{workspace_name}//:bzl"],
)
"""
package_filegroups = [
one_package_template.format(
package_name = p,
workspace_name = hazel_workspace(p),
)
for p in ctx.attr.packages
]
ctx.file("BUILD", "\n".join([all_packages_filegroup]+package_filegroups), executable=False)

_all_hazel_packages = repository_rule(
implementation=_all_hazel_packages_impl,
attrs={
"files": attr.label_list(mandatory=True),
"packages": attr.string_list(mandatory=True),
})

def _fixup_package_name(package_name):
"""Fixup package name by replacing dashes with underscores to get a valid
workspace name from it.
Args:
package_name: string: Package name.
Returns:
string: fixed package name.
"""
return package_name.replace("-", "_")

def hazel_repositories(
core_packages,
packages,
Expand All @@ -105,7 +107,7 @@ def hazel_repositories(
external dependencies corresponding to the given packages:
- @hazel_base_repository: The compiled "hazel" Haskell binary, along with
support files.
- @haskell_{package}: A build of the given Cabal package, one per entry
- @haskell_{package}_{hash}: A build of the given Cabal package, one per entry
of the "packages" argument. (Note that Bazel only builds these
on-demand when needed by other rules.) This repository automatically
downloads the package's Cabal distribution from Hackage and parses the
Expand Down Expand Up @@ -155,7 +157,7 @@ def hazel_repositories(
flags.update({flag: str(items[flag]) for flag in items})

_cabal_haskell_repository(
name = "haskell_" + _fixup_package_name(p),
name = hazel_workspace(p),
package_name = p,
package_version = pkgs[p].version,
package_flags = flags,
Expand All @@ -165,17 +167,13 @@ def hazel_repositories(

for p in core_packages:
_core_library_repository(
name = "haskell_" + _fixup_package_name(p),
name = hazel_workspace(p),
package = p,
)

_all_hazel_packages(
name = "all_hazel_packages",
files = ["@haskell_{}//:files".format(_fixup_package_name(p)) for p in pkgs])

def hazel_library(name):
"""Returns the label of the haskell_library rule for the given package."""
return "@haskell_{}//:{}".format(_fixup_package_name(name), name)
packages = [p for p in pkgs])

def hazel_custom_package_hackage(
package_name,
Expand All @@ -193,10 +191,9 @@ def hazel_custom_package_hackage(
package_id,
package_id,
)
fixed_package_name = _fixup_package_name(package_name)
http_archive(
name = "haskell_{0}".format(fixed_package_name),
build_file = "//third_party/haskell:BUILD.{0}".format(fixed_package_name),
name = hazel_workspace(package_name),
build_file = "//third_party/haskell:BUILD.{0}".format(package_name),
sha256 = sha256,
strip_prefix = package_id,
urls = [url],
Expand Down Expand Up @@ -224,13 +221,12 @@ def hazel_custom_package_github(
repos).
"""

fixed_package_name = _fixup_package_name(package_name)
build_file = "//third_party/haskell:BUILD.{0}".format(fixed_package_name)
build_file = "//third_party/haskell:BUILD.{0}".format(package_name)
url = "https://github.com/{0}/{1}".format(github_user, github_repo)
ssh_url = "[email protected]:{0}/{1}".format(github_user, github_repo)

new_git_repository(
name = "haskell_{0}".format(fixed_package_name),
name = hazel_workspace(package_name),
remote = ssh_url if clone_via_ssh else url,
build_file = build_file,
commit = repo_sha,
Expand Down
2 changes: 1 addition & 1 deletion third_party/cabal2bazel/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ some key differences:

#### Hazel repository structure
- The components of each package live in an autogenerated external repository
(for example: `@haskell_{package}//...`), rather than
(for example: `@haskell_{package}_{hash}//...`), rather than
`//third_party/haskell/{package}/...`.
- Default packages are taken from the `packages.bzl` file, rather than being
hard-coded; they also need to be passed via the `builtin_dependencies`
Expand Down
5 changes: 3 additions & 2 deletions third_party/cabal2bazel/bzl/alex.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@
# srcs = [ "MyScanner.y.hs" ]
# ...
# )
load("//tools:mangling.bzl", "hazel_binary")
def genalex(src, out):
native.genrule(
name=out + ".hs_alex",
srcs=[src],
outs=[out],
tools=["@haskell_alex//:alex_bin"],
cmd="$(location @haskell_alex//:alex_bin) -g -o $(OUTS) $(SRCS)",
tools=[hazel_binary("alex")],
cmd="$(location {}) -g -o $(OUTS) $(SRCS)".format(hazel_binary("alex")),
)
7 changes: 5 additions & 2 deletions third_party/cabal2bazel/bzl/cabal_package.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
"""Skylark build rules for cabal haskell packages.
To see all of the generated rules, run:
bazel query --output=build @haskell_{package}//:all
bazel query --output=build @haskell_{package}_{hash}//:all
where {package} is the lower-cased package name with - replaced by _
and {hash} is the Bazel hash of the original package name.
"""
load("@bazel_skylib//:lib.bzl", "paths")
load("@io_tweag_rules_haskell//haskell:haskell.bzl",
Expand All @@ -28,6 +30,7 @@ load(":bzl/alex.bzl", "genalex")
load(":bzl/cabal_paths.bzl", "cabal_paths")
load(":bzl/happy.bzl", "genhappy")
load("//templates:templates.bzl", "templates")
load("//tools:mangling.bzl", "hazel_library")

_conditions_default = "//conditions:default"

Expand Down Expand Up @@ -238,7 +241,7 @@ def _get_build_attrs(
if condition not in deps:
deps[condition] = []
for p in ps:
deps[condition] += ["@haskell_{}//:{}".format(p.replace("-", "_"), p)]
deps[condition] += [hazel_library(p)]
if p in _CORE_DEPENDENCY_INCLUDES:
cdeps += [_CORE_DEPENDENCY_INCLUDES[p]]
deps[condition] += [_CORE_DEPENDENCY_INCLUDES[p]]
Expand Down
5 changes: 3 additions & 2 deletions third_party/cabal2bazel/bzl/cabal_paths.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ For usage information, see the below documentation for that rule.
"""
load("@bazel_skylib//:lib.bzl", "paths")
load("@io_tweag_rules_haskell//haskell:haskell.bzl", "haskell_library")
load("//tools:mangling.bzl", "hazel_library")

def _impl_path_module_gen(ctx):
paths_file = ctx.new_file(ctx.label.name)
Expand Down Expand Up @@ -108,8 +109,8 @@ def cabal_paths(name=None, package=None, data_dir='',data=[], version=[], **kwar
srcs = [paths_file],
data = data,
deps = [
"@haskell_base//:base",
"@haskell_filepath//:filepath",
hazel_library("base"),
hazel_library("filepath"),
],
# TODO: run directory resolution.
**kwargs)
6 changes: 4 additions & 2 deletions third_party/cabal2bazel/bzl/happy.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ Example:
)
"""

load("//tools:mangling.bzl", "hazel_binary")

def genhappy(src, out):
native.genrule(
name = out + ".hs_happy",
srcs = [src],
outs = [out],
tools = ["@haskell_happy//:happy_bin"],
cmd = "$(location @haskell_happy//:happy_bin) -g -a -c -o $(OUTS) $(SRCS)",
tools = [hazel_binary("happy")],
cmd = "$(location {}) -g -a -c -o $(OUTS) $(SRCS)".format(hazel_binary("happy")),
)
31 changes: 16 additions & 15 deletions third_party/haskell/BUILD.conduit
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"])
load("@io_tweag_rules_haskell//haskell:haskell.bzl",
"haskell_library",
)
load("//tools:mangling.bzl", "hazel_library")

haskell_library(
name = "conduit",
Expand All @@ -13,21 +14,21 @@ haskell_library(
]),
compiler_flags = ["-Iexternal/haskell_conduit"],
deps = [
"@haskell_base//:base",
"@haskell_bytestring//:bytestring",
"@haskell_exceptions//:exceptions",
"@haskell_lifted_base//:lifted-base",
"@haskell_mmorph//:mmorph",
"@haskell_monad_control//:monad-control",
"@haskell_mono_traversable//:mono-traversable",
"@haskell_mtl//:mtl",
"@haskell_primitive//:primitive",
"@haskell_resourcet//:resourcet",
"@haskell_text//:text",
"@haskell_transformers//:transformers",
"@haskell_transformers_base//:transformers-base",
"@haskell_transformers_compat//:transformers-compat",
"@haskell_vector//:vector",
hazel_library("base"),
hazel_library("bytestring"),
hazel_library("exceptions"),
hazel_library("lifted_base"),
hazel_library("mmorph"),
hazel_library("monad_control"),
hazel_library("mono_traversable"),
hazel_library("mtl"),
hazel_library("primitive"),
hazel_library("resourcet"),
hazel_library("text"),
hazel_library("transformers"),
hazel_library("transformers_base"),
hazel_library("transformers_compat"),
hazel_library("vector"),
],
version = "1.2.13.1",
)
Empty file added tools/BUILD
Empty file.
57 changes: 57 additions & 0 deletions tools/mangling.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
def hazel_library(package_name):
"""Returns the label of the haskell_library rule for the given package."""
return "@{}//:{}".format(hazel_workspace(package_name), package_name)


def hazel_binary(package_name):
"""Returns the label of the haskell_binary rule for the given package."""
return "@{}//:{}_bin".format(hazel_workspace(package_name), package_name)


def hazel_workspace(package_name):
"""Convert a package name to a valid and unambiguous workspace name.
Makes the name unambiguous to case-insensitive file-systems and
converts the package name into a valid Bazel workspace name.
Args:
package_name: string: Package name.
Returns:
string: Workspace name.
"""
return "haskell_{}".format(
fixup_package_name(case_insensitive_name(package_name))
)

def fixup_package_name(package_name):
"""Convert a package name to a valid workspace name.
Replaces dashes with underscores.
Args:
package_name: string: Package name.
Returns:
string: A valid workspace name.
"""
return package_name.replace("-", "_")


def case_insensitive_name(package_name):
"""Convert a package name to a case-insensitive name.
Appends the hash of the input string, and converts the whole string to
lower case. Note, the appended hash value is represented in decimal, and
may be negative.
Args:
name: string: A potentially case-sensitive package name.
Returns:
string: A case-insensitive package name.
"""
return "{lower}_{hash}".format(
lower = package_name.lower(),
hash = hash(package_name)
)

0 comments on commit 561366d

Please sign in to comment.