Skip to content

Commit

Permalink
Merge pull request #3 from tweag/rules
Browse files Browse the repository at this point in the history
Transitive library depends.
  • Loading branch information
Fuuzetsu authored Nov 15, 2017
2 parents 774648f + 81dbacb commit d17400e
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 63 deletions.
7 changes: 2 additions & 5 deletions examples/hello-lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@ load(

haskell_library(
name = 'hello-lib',
srcs = [
'MsgType.hs',
'Unused.hs',
'Lib.hs'
]
srcs = glob(['**/*.hs']),
deps = ["@examples//hello-sublib:hello-sublib"]
)
5 changes: 3 additions & 2 deletions examples/hello-lib/Lib.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Lib (libText) where

import MsgType (Msg)
import Lib.MsgType (Msg)
import MessageSuffix (messageSuffix)

libText :: Msg
libText = "hello world"
libText = "hello " ++ messageSuffix
4 changes: 4 additions & 0 deletions examples/hello-lib/Lib/MsgType.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module Lib.MsgType (Msg) where


type Msg = String
4 changes: 0 additions & 4 deletions examples/hello-lib/MsgType.hs

This file was deleted.

12 changes: 12 additions & 0 deletions examples/hello-sublib/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package(default_visibility = ["//visibility:public"])

load(
"@io_tweag_rules_haskell//haskell:haskell.bzl",
"haskell_library",
)

haskell_library(
name = 'hello-sublib',
srcs = glob(['**/*.hs']),
deps = ["@examples//hello-subsublib:hello-subsublib"]
)
6 changes: 6 additions & 0 deletions examples/hello-sublib/MessageSuffix.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module MessageSuffix (messageSuffix) where

import MessageEnd (messageEnd)

messageSuffix :: String
messageSuffix = "world" ++ messageEnd
11 changes: 11 additions & 0 deletions examples/hello-subsublib/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package(default_visibility = ["//visibility:public"])

load(
"@io_tweag_rules_haskell//haskell:haskell.bzl",
"haskell_library",
)

haskell_library(
name = 'hello-subsublib',
srcs = glob(['**/*.hs']),
)
4 changes: 4 additions & 0 deletions examples/hello-subsublib/MessageEnd.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module MessageEnd (messageEnd) where

messageEnd :: String
messageEnd = "!!"
89 changes: 62 additions & 27 deletions haskell/haskell.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,38 @@ load(":toolchain.bzl",
)

def _haskell_binary_impl(ctx):
depInputs = []
systemLibs = []
depInputs = depset()
depLibs = depset()
for d in ctx.attr.deps:
# depend on output of deps, i.e. package file
depInputs += d.files.to_list()
# We need interface files of the package for compilation
depInputs += d[HaskellPackageInfo].interfaceFiles
# Lastly we need library object for linking
systemLibs.append(d[HaskellPackageInfo].systemLib)
depInputs += d.files
pkg = d[HaskellPackageInfo]
depInputs += pkg.pkgConfs
depInputs += pkg.pkgCaches
depInputs += pkg.pkgLibs
depInputs += pkg.interfaceFiles
depLibs += pkg.pkgLibs

depInputs += ctx.files.srcs

objDir = ctx.actions.declare_directory("objects")
binObjs = [ctx.actions.declare_file(src_to_ext(ctx, s, "o", directory=objDir))
for s in ctx.files.srcs]

# Compile sources of the binary.
ctx.actions.run(
inputs = ctx.files.srcs + depInputs + systemLibs,
inputs = depInputs + depLibs,
outputs = binObjs + [objDir],
# TODO: use env for GHC_PACKAGE_PATH when use_default_shell_env is removed
use_default_shell_env = True,
progress_message = "Building {0}".format(ctx.attr.name),
executable = "ghc",
arguments = [ghc_bin_obj_args(ctx, objDir)],
arguments = [ghc_bin_obj_args(ctx, objDir)]
)

# Link everything together
linkTarget, linkArgs = ghc_bin_link_args(ctx, binObjs, systemLibs)
linkTarget, linkArgs = ghc_bin_link_args(ctx, binObjs, depLibs)
ctx.actions.run(
inputs = binObjs + systemLibs + [linkTarget],
inputs = binObjs + depLibs.to_list() + [linkTarget],
outputs = [ctx.outputs.executable],
use_default_shell_env = True,
progress_message = "Linking {0}".format(ctx.outputs.executable),
Expand All @@ -55,47 +59,78 @@ def _haskell_library_impl(ctx):
interfaceFiles = [ctx.actions.declare_file(src_to_ext(ctx, s, "hi", directory=ifaceDir))
for s in ctx.files.srcs ]

# Compile library files
#
# TODO: Library deps

# Build transitive depsets
depPkgConfs = depset()
depPkgCaches = depset()
depPkgNames = depset()
depPkgLibs = depset()
depInterfaceFiles = depset()
depImportDirs = depset()
depLibDirs = depset()
for d in ctx.attr.deps:
pkg = d[HaskellPackageInfo]
depPkgConfs += pkg.pkgConfs
depPkgCaches += pkg.pkgCaches
depPkgNames += pkg.pkgNames
depInterfaceFiles += pkg.interfaceFiles
depPkgLibs += pkg.pkgLibs
depImportDirs += pkg.pkgImportDirs
depLibDirs += pkg.pkgLibDirs

# Compile library files
ctx.actions.run(
inputs = ctx.files.srcs,
inputs =
ctx.files.srcs +
(depPkgConfs + depPkgCaches + depInterfaceFiles).to_list(),
outputs = [ifaceDir, objDir] + objectFiles + interfaceFiles,
use_default_shell_env = True,
progress_message = "Compiling {0}".format(ctx.attr.name),
executable = "ghc",
arguments = [ghc_lib_args(ctx, objDir, ifaceDir)]
arguments = [ghc_lib_args(ctx, objDir, ifaceDir, depPkgConfs, depPkgNames)]
)

# Make library archive; currently only static
#
# TODO: configurable shared &c. see various scenarios in buck
libDir = ctx.actions.declare_directory("lib")
systemLib = ctx.actions.declare_file("{0}/lib{1}.a".format(libDir.basename, ctx.attr.name))
pkgLib = ctx.actions.declare_file("{0}/lib{1}.a".format(libDir.basename, ctx.attr.name))

ctx.actions.run(
inputs = objectFiles,
outputs = [systemLib, libDir],
outputs = [pkgLib, libDir],
use_default_shell_env = True,
executable = "ar",
arguments = [ar_args(ctx, systemLib, objectFiles)],
arguments = [ar_args(ctx, pkgLib, objectFiles)],
)

# Create and register ghc package.
pkgId = "{0}-{1}".format(ctx.attr.name, ctx.attr.version)
confFile = ctx.actions.declare_file("{0}.conf".format(pkgId))
cacheFile = ctx.actions.declare_file("package.cache")
cacheFile = ctx.actions.declare_file("package.cache", sibling=confFile)
registrationFile = mk_registration_file(ctx, pkgId, ifaceDir, libDir)
ctx.actions.run_shell(
inputs = [systemLib, ifaceDir, registrationFile],
inputs =
[ pkgLib, ifaceDir, registrationFile ] +
(depPkgConfs + depPkgCaches + depPkgLibs + depImportDirs + depLibDirs + interfaceFiles).to_list(),
outputs = [confFile, cacheFile],
# TODO: use env for GHC_PACKAGE_PATH when use_default_shell_env is removed
use_default_shell_env = True,
command = register_package(ifaceDir, registrationFile, confFile.dirname)
command = register_package(registrationFile, confFile, depPkgConfs)
)
return [HaskellPackageInfo( packageName = pkgId,
pkgDb = confFile.dirname,
systemLib = systemLib,
interfaceFiles = interfaceFiles)]

return [HaskellPackageInfo(
pkgName = pkgId,
pkgNames = depPkgNames + depset([pkgId]),
pkgConfs = depPkgConfs + depset([confFile]),
pkgCaches = depPkgCaches + depset([cacheFile]),
# Keep package libraries in preorder (naive_link) order: this
# gives us the valid linking order at binary linking time.
pkgLibs = depset([pkgLib], order="preorder") + depPkgLibs,
interfaceFiles = depInterfaceFiles + depset(interfaceFiles),
pkgImportDirs = depImportDirs + depset([ifaceDir]),
pkgLibDirs = depLibDirs + depset([libDir])
)]

_haskell_common_attrs = {
"srcs": attr.label_list(allow_files = FileType([".hs"])),
Expand Down
71 changes: 48 additions & 23 deletions haskell/toolchain.bzl
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
HaskellPackageInfo = provider(
doc = "Package information exposed by Haskell libraries.",
fields = {
"packageName": "Package name, usually of the form name-version.",
"pkgDb": "Directory containing the registered package database.",
"systemLib": "Compiled library archive.",
"interfaceFiles": "Interface files belonging to the package."
"pkgName": "Package name, usually of the form name-version.",
"pkgNames": "All package names of transitive dependencies. Includes own name.",
"pkgConfs": "Package conf files.",
"pkgCaches": "Package cache files.",
"pkgLibs": "Compiled library archives.",
"interfaceFiles": "Interface files belonging to the packages.",
"pkgImportDirs": "",
"pkgLibDirs": ""
}
)

Expand All @@ -19,12 +23,18 @@ def ghc_bin_obj_args(ctx, objDir):
args.add("-no-link")
args.add(ctx.files.srcs)
args.add(["-odir", objDir])
for d in ctx.attr.deps:
args.add(["-package", d[HaskellPackageInfo].packageName])
args.add(["-package-db", d[HaskellPackageInfo].pkgDb])

# Collapse all library dependencies
depNames = depset([ n for d in ctx.attr.deps for n in d[HaskellPackageInfo].pkgNames ])
depConfs = depset([ c.dirname for d in ctx.attr.deps for c in d[HaskellPackageInfo].pkgConfs ])

for n in depNames:
args.add(["-package", n])
for db in depConfs:
args.add(["-package-db", db])
return args

def ghc_bin_link_args(ctx, binObjs, systemLibs):
def ghc_bin_link_args(ctx, binObjs, depLibs):
"""Build arguments for Haskell binary linking stage.
Also creates an empty library archive to as a build target: this
Expand All @@ -36,7 +46,7 @@ def ghc_bin_link_args(ctx, binObjs, systemLibs):
Args:
ctx: Rule context.
binObjs: Object files to include during linking.
systemLibs: Library archives to include during linking.
depLibs: Library archives to include during linking.
"""
# Create empty archive so that GHC has some input files to work on during linking
Expand All @@ -55,7 +65,6 @@ def ghc_bin_link_args(ctx, binObjs, systemLibs):
executable = "ghc",
arguments = [dummyArgs]
)
# TODO: Buck also calls ranlib on the output: should we?
ctx.actions.run(
inputs = [dummyObj],
outputs = [dummyLib],
Expand All @@ -69,21 +78,29 @@ def ghc_bin_link_args(ctx, binObjs, systemLibs):
args.add(dummyLib)
for o in binObjs:
args.add(["-optl", o])
for l in systemLibs:
for l in depLibs:
args.add(["-optl", l])
return dummyLib, args

def ghc_lib_args(ctx, objDir, ifaceDir):
def ghc_lib_args(ctx, objDir, ifaceDir, pkgConfs, pkgNames):
"""Build arguments for Haskell package build.
Args:
ctx: Rule context.
objDir: Output directory for object files.
ifaceDir: Output directory for interface files.
pkgConfs: Package conf files of dependencies.
pkgNames: Package names of dependencies.
"""
args = ctx.actions.args()
args.add(["-no-link"])
args.add(["-package-name", "{0}-{1}".format(ctx.attr.name, ctx.attr.version)])
args.add(["-odir", objDir, "-hidir", ifaceDir])

for n in pkgNames:
args.add(["-package", n])
for c in pkgConfs:
args.add(["-package-db", c.dirname])
args.add("-i")
args.add(ctx.files.srcs)
return args
Expand Down Expand Up @@ -128,7 +145,7 @@ def mk_registration_file(ctx, pkgId, interfaceDir, libDir):
"import-dirs": "${{pkgroot}}/{0}/{1}".format(ctx.label.name, interfaceDir.basename),
"library-dirs": "${{pkgroot}}/{0}/{1}".format(ctx.label.name, libDir.basename),
"hs-libraries": ctx.attr.name,
"depends": "" # TODO
"depends": ", ".join([ d[HaskellPackageInfo].pkgName for d in ctx.attr.deps ])
}
ctx.actions.write(
output=registrationFile,
Expand All @@ -137,32 +154,39 @@ def mk_registration_file(ctx, pkgId, interfaceDir, libDir):
)
return registrationFile

def register_package(ifaceDir, registrationFile, pkgDir):
def register_package(registrationFile, confFile, pkgConfs):
"""Initialises, registers and checks ghc DB package.
Args:
ifaceDir: undefined
registrationFile: undefined
pkgDir: undefined
registrationFile: File containing package description.
confFile: The conf file to use for this package.
pkgConfs: Package config files of dependencies this package needs.
"""
pkgDir = confFile.dirname
scratchDir = "ghc-pkg-init-scratch"
initPackage = "ghc-pkg init {0}".format(scratchDir)
# Move things out of scratch to make it easier for everyone. ghc-pkg
# refuses to use an existing directory.
mvFromScratch = "mv {0}/* {1}".format(scratchDir, pkgDir)

# TODO: Set GHC_PACKAGE_PATH with ctx.actions.run_shell.env when we
# stop using use_default_shell_env! That way we can't forget to set
# this.
packagePath = ":".join([ conf.dirname for conf in pkgConfs ])
ghcPackagePath = "GHC_PACKAGE_PATH={0}".format(packagePath)

registerPackage = " ".join(
[ "GHC_PACKAGE_PATH=''",
[ ghcPackagePath,
"ghc-pkg",
"-v0",
"register",
"--package-conf={0}".format(pkgDir),
"--no-expand-pkgroot",
"--force-files",
registrationFile.path,
]
)
# make sure what we produce is valid
checkPackage = "ghc-pkg check --package-conf={0}".format(pkgDir)
checkPackage = "{0} ghc-pkg check --package-conf={1}".format(ghcPackagePath, pkgDir)
return " && ".join(
[ initPackage,
mvFromScratch,
Expand All @@ -186,15 +210,16 @@ def src_to_ext(ctx, src, ext, directory=None):
else:
return "{0}/{1}".format(directory.basename, fp)

def ar_args(ctx, systemLib, objectFiles):
# We might want ranlib like Buck does.
def ar_args(ctx, pkgLib, objectFiles):
"""Create arguments for `ar` tool.
Args:
systemLib: The declared static library to generate.
pkgLib: The declared static library to generate.
objectFiles: Object files to pack into the library.
"""
args = ctx.actions.args()
args.add("qc")
args.add(systemLib)
args.add(pkgLib)
args.add(objectFiles)
return args
3 changes: 2 additions & 1 deletion tests/test-lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ haskell_library(
name = 'test-lib',
srcs = [
'TestLib.hs',
]
],
deps = ["//tests/test-sublib:test-sublib"]
)
4 changes: 3 additions & 1 deletion tests/test-lib/TestLib.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
module TestLib (testMessage) where

import TestSubLib (messageEnd)

testMessage :: String
testMessage = "hello"
testMessage = "hello " ++ messageEnd
Loading

0 comments on commit d17400e

Please sign in to comment.