Skip to content

Commit

Permalink
Transitive library depends.
Browse files Browse the repository at this point in the history
Tested sequential exclusive ordering only, did not try trees and
mutual dependencies so far.
  • Loading branch information
Mateusz Kowalczyk committed Nov 14, 2017
1 parent 774648f commit 81dbacb
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 81dbacb

Please sign in to comment.