diff --git a/cfg_constructor_if_standalone.bzl b/cfg_constructor_if_standalone.bzl index a0b4967..50a4389 100644 --- a/cfg_constructor_if_standalone.bzl +++ b/cfg_constructor_if_standalone.bzl @@ -29,17 +29,10 @@ load( load("@prelude//cfg/modifier/common.bzl", "MODIFIER_METADATA_KEY") def cfg_constructor_if_standalone(): - # get_cell_name() gives the name of *this* PACKAGE file's own cell - - # "root" when this checkout's own .buckconfig is the outermost one - # (genuinely standalone), or "hsthrift" when nested inside another - # project's cell graph (see buck2.md's hsthrift-cell entry for why - # cell names are global to whichever project is outermost - that's - # exactly what makes this check correct: a plain read_config() flag - # in this project's own .buckconfig does *not* work here, since a - # cell's own .buckconfig is still consulted for ordinary config - # values even when nested, unlike the special-cased [cells] section - - # confirmed directly, the first version of this check used that and - # it read as true (and hard-errored the nested build) either way. + # get_cell_name() gives the name of *this* PACKAGE file's own cell + # - "root" when this checkout's own .buckconfig is the outermost + # one (genuinely standalone), or the name of the cell when nested + # inside another project's cell graph. if get_cell_name() != "root": return diff --git a/haskell.bzl b/haskell.bzl index 99013e2..c02d6c4 100644 --- a/haskell.bzl +++ b/haskell.bzl @@ -29,13 +29,21 @@ def _package_deps(packages): all_pkgs = {p: None for p in (AUTO_PACKAGES + packages)} return [("@third-party-haskell//:" + p) for p in sorted(all_pkgs.keys())] -# Build modes (buck2.md TODO "we should support different build modes"), -# selected via `buck2 build ... -m root//buck2/constraints:opt` (`dev` is -# the default - see the root PACKAGE file). `dev` matches this migration's -# original, only behaviour (shared libs, no optimisation - fast to -# rebuild); `opt` is what an actual deployed `glean` binary wants (a single -# static binary, optimised). Centralized here rather than passed by each -# BUCK file, the same reasoning as FB_HASKELL_EXTENSIONS above - one place +def _cabal_macros_include_flags(cabal_component): + if cabal_component == None: + return [] + pkg, component = cabal_component + autogen_dir = "cabal-buck2/autogen" if pkg == "." else pkg + "/cabal-buck2/autogen" + label = "//" + autogen_dir + ":" + component + "-cabal-macros" + return ["-optP-include", "-optP$(location " + label + ")"] + +# Build modes selected via `buck2 build ... -m +# root//buck2/constraints:opt` (`dev` is the default - see the root +# PACKAGE file). `dev` matches this migration's original, only +# behaviour (shared libs, no optimisation - fast to rebuild); `opt` is +# what an actual deployed `glean` binary wants (a single static +# binary, optimised). Centralized here rather than passed by each BUCK +# file, the same reasoning as FB_HASKELL_EXTENSIONS above - one place # to change, automatically applied to every haskell_library()/ # haskell_binary() in the tree. # @@ -102,17 +110,8 @@ _BUILD_MODE_PREFERRED_LINKAGE = select({ }), }) -# The `-dynamic-too`/native-shared-libs-symlink-tree machinery this -# section used to force on unconditionally (via a `dynamic_too = True` -# kwarg here) is now driven entirely by `haskell_toolchain.dynamic_ghc` -# instead - a LOCAL FORK in `buck2/prelude/haskell/{toolchain,haskell} -# .bzl` (see buck2.md) auto-detected per GHC installation by buck2/ -# gen-haskell-prebuilt.py (`ghc --info`'s own `("GHC Dynamic",...)` -# report), not hardcoded here. No kwarg needed any more - the prelude's -# own `haskell_library_impl`/`haskell_binary_impl` read the toolchain -# directly. Kept for reference, since the *reasoning* for why this -# needs to be on whenever GHC itself is dynamically linked is still the -# same as when this was a hardcoded `True`: +# Why the toolchain needs to know whether GHC was linked dynamically +# (dynamic_ghc): # # 1. Template Haskell splices need every package loadable the *dynamic* # way: a dynamically-linked `ghc` binary's internal splice @@ -187,11 +186,7 @@ _PROF_ENABLED = select({ "DEFAULT": False, }) -# Matches glean.cabal.in's `common exe`: `if flag(asan) ghc-options: -# -optc-fsanitize=address -optl-fsanitize=address` - only executables -# (haskell_binary(), below) link a C/C++ runtime that asan instruments, -# so (matching Cabal's own `common exe`, never applied to a library -# component) this is only added there, not in haskell_library(). +# Linker flags enabled when building with ASAN _ASAN_LINKER_FLAGS = select({ "root//buck2/constraints:asan": ["-optc-fsanitize=address", "-optl-fsanitize=address"], "DEFAULT": [], @@ -278,6 +273,13 @@ def haskell_library( # hsc2hs.bzl's `extra_flags`) - the buck2 equivalent of Cabal's # per-library `hsc2hs-options` field. hsc_flags = [], + # `cabal_component = (pkg, component)` causes this component's + # `cabal_macros.h` file to be included when `{-# LANGUAGE CPP #-}` + # is on, which provides access to the MIN_VERSION_pkg(x,y,z) macros + # amongst other things. However, GHC also provides the MIN_VERSION + # macros by default, so unless you need anything else from + # cabal_macros.h there's no need to use this. + cabal_component = None, **kwargs): # No `link_style` here - unlike haskell_binary(), haskell_library() # doesn't take one at all: a library builds whichever output styles its @@ -293,7 +295,7 @@ def haskell_library( native.haskell_library( name = name, srcs = _resolve_srcs(name, srcs, all_deps, hsc_flags), - compiler_flags = compiler_flags + _BUILD_MODE_HASKELL_FLAGS, + compiler_flags = compiler_flags + _BUILD_MODE_HASKELL_FLAGS + _cabal_macros_include_flags(cabal_component), deps = all_deps, **kwargs ) @@ -306,6 +308,7 @@ def haskell_binary( compiler_flags = [], hsc_flags = [], linker_flags = [], + cabal_component = None, # see haskell_library() **kwargs): all_deps = deps + _package_deps(packages) kwargs.setdefault("link_style", _BUILD_MODE_LINK_STYLE) @@ -323,7 +326,7 @@ def haskell_binary( # (`-optl-...` during a compile-only invocation, `-optc-...` when # nothing needs the C compiler), so adding both everywhere is the # faithful equivalent, not redundant belt-and-braces. - compiler_flags = compiler_flags + _BUILD_MODE_HASKELL_FLAGS + _ASAN_LINKER_FLAGS, + compiler_flags = compiler_flags + _BUILD_MODE_HASKELL_FLAGS + _ASAN_LINKER_FLAGS + _cabal_macros_include_flags(cabal_component), deps = all_deps, linker_flags = _ASAN_LINKER_FLAGS + linker_flags, **kwargs @@ -334,19 +337,14 @@ def haskell_binary( # needs nothing Haskell-specific: this builds the exact same # haskell_binary() `name` would (so `buck2 run :name` is unaffected), plus # a same-named `:name-test` native.sh_test() wrapping it, which is enough -# for `buck2 test :name-test` to work with zero .buckconfig changes (see -# buck2.md's "buck test" entry for why a plain sh_test() wrapper was -# chosen over writing a bespoke rule - a custom rule would still need this -# same two-target shape under the hood, since a rule can't invoke another -# rule's impl inline, so it would just mean re-implementing sh_test's own -# ExternalRunnerTestInfo wiring ourselves for no functional gain). +# for `buck2 test :name-test` to work with zero .buckconfig changes. # # `test_args`/`test_env` cover the one real wrinkle: a test-suite that # shells out to another buck2-built tool (e.g. glean-clang's clang-index) # needs that tool's location passed in explicitly via a `$(exe ...)` # string-parameter macro, rather than relying on it being on `$PATH` - # more hermetic than this migration's own earlier practice of manually -# prepending PATH by hand to reproduce these runs (see buck2.md). +# prepending PATH by hand to reproduce these runs. # # `LANG` defaults to a UTF-8 locale: unlike `buck2 run` (which inherits # the caller's shell environment, `LANG` included), `buck2 test` runs diff --git a/prelude/decls/haskell_common.bzl b/prelude/decls/haskell_common.bzl index 3a0732d..87df9cb 100644 --- a/prelude/decls/haskell_common.bzl +++ b/prelude/decls/haskell_common.bzl @@ -38,8 +38,11 @@ def _deps_arg(): def _compiler_flags_arg(): return { + # attrs.arg(), not attrs.string(): lets a flag carry a + # `$(location ...)`/`$(exe ...)` macro that resolves to a real + # dependency edge. "compiler_flags": attrs.list( - attrs.string(), + attrs.arg(), default = [], doc = """ Flags to pass to the Haskell compiler when compiling this rule's sources. diff --git a/prelude/rules_impl.bzl b/prelude/rules_impl.bzl index c9f1221..e803257 100644 --- a/prelude/rules_impl.bzl +++ b/prelude/rules_impl.bzl @@ -450,14 +450,6 @@ _haskell_extra_attributes = { }, "haskell_library": { "preferred_linkage": attrs.enum(Linkage.values(), default = "any"), - # No `dynamic_too` attr here any more (LOCAL FORK - see - # buck2.md): whether to build the static archive with - # `-dynamic-too` (deriving the shared library from that same - # compile's .dyn_o output, instead of compiling "static" and - # "shared" independently) is now driven directly from - # `haskell_toolchain.dynamic_ghc` in haskell.bzl's own - # `haskell_library_impl`, not a per-target attribute - see that - # field's own comment (toolchain.bzl) for why. "template_deps": attrs.list(attrs.exec_dep(providers = [HaskellLibraryProvider]), default = []), "_cxx_toolchain": toolchains_common.cxx(), "_haskell_toolchain": toolchains_common.haskell(), diff --git a/thrift.bzl b/thrift.bzl index a3f4057..ee15ca7 100644 --- a/thrift.bzl +++ b/thrift.bzl @@ -44,19 +44,20 @@ _GEN_PREFIX = "gen-hs2" # "."; see also Thrift.Compiler.parseThriftFileE: `baseDir path`, and # FilePath.() only ignores baseDir when `path` is already absolute). # -# A directory named directly on the command line (a plain "-I some/dir" -# string, or a glob'd filegroup covering "everything in this package") is -# a bad fit for that: a plain path can't tell "this cell built standalone" -# apart from "this cell built nested inside another project at a different -# filesystem depth" (see buck2.md's hsthrift-cell entry), and a glob'd -# directory isn't hermetic - remote execution materializes only an -# action's *declared* inputs, not whatever real files happen to sit next -# to the main one in the source tree (which is also what made a shared -# "thrift" symlink inside both lib/ and tests/ - see thrift/BUCK at the -# cell root - actively break: Buck2 saw one input (a glob covering the -# symlink itself) wanting it to be a leaf/symlink, and another input -# (an explicit thrift_file reference reached *through* it) wanting the -# same path to be a real directory, and refused to merge the two). +# A directory named directly on the command line (a plain "-I +# some/dir" string, or a glob'd filegroup covering "everything in this +# package") is a bad fit for that: a plain path can't tell "this cell +# built standalone" apart from "this cell built nested inside another +# project at a different filesystem depth", and a glob'd directory +# isn't hermetic - remote execution materializes only an action's +# *declared* inputs, not whatever real files happen to sit next to the +# main one in the source tree (which is also what made a shared +# "thrift" symlink inside both lib/ and tests/ - see thrift/BUCK at +# the cell root - actively break: Buck2 saw one input (a glob covering +# the symlink itself) wanting it to be a leaf/symlink, and another +# input (an explicit thrift_file reference reached *through* it) +# wanting the same path to be a real directory, and refused to merge +# the two). # # So instead: every file an `include` statement might ever need is reached # through `deps` - other thrift_srcs_export()/thrift_library() targets,