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
125 changes: 110 additions & 15 deletions rocq/private/rocq.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,16 @@ RocqInfo = provider(
doc = "Information about Rocq compilation results",
fields = {
"sources": "depset of .v source files",
"compiled": "depset of .vo compiled files",
"include_paths": "list of include paths for dependent libraries",
"transitive_deps": "depset of all transitive Rocq dependencies",
},
)

# Core Rocq library compilation rule
def _rocq_library_impl(ctx):
"""Collect .v files for Rocq proof library.
"""Compile Rocq .v files to .vo using coqc.

Currently collects source files for later compilation with Rocq/Coq.
Full compilation support requires toolchain configuration.
Uses the Rocq toolchain from nixpkgs to compile proofs.
"""
# Validate source files
sources = ctx.files.srcs
Expand All @@ -32,48 +31,137 @@ def _rocq_library_impl(ctx):
if not src.path.endswith(".v"):
fail("rocq_library only accepts .v files, got: " + src.path)

# Process dependencies to get transitive sources
# Get toolchain info
toolchain = ctx.toolchains["@rules_rocq_rust//rocq:toolchain_type"]

# If no toolchain available, fall back to source collection only
if not toolchain or not hasattr(toolchain, "rocq_info"):
return _rocq_library_source_only(ctx, sources)

rocq_info = toolchain.rocq_info
coqc = rocq_info.coqc

if not coqc:
# No coqc found, fall back to source collection
return _rocq_library_source_only(ctx, sources)

# Process dependencies
transitive_sources = []
transitive_compiled = []
include_paths = []

for dep in ctx.attr.deps:
if RocqInfo in dep:
transitive_sources.append(dep[RocqInfo].transitive_deps)
if dep[RocqInfo].compiled:
transitive_compiled.append(dep[RocqInfo].compiled)
include_paths.extend(dep[RocqInfo].include_paths)

# Add current library's include path
include_paths.append(ctx.attr.include_path if ctx.attr.include_path else ctx.label.package)
pkg_path = ctx.label.package
include_paths.append(ctx.attr.include_path if ctx.attr.include_path else pkg_path)

# Compile each .v file to .vo
compiled_files = []
for src in sources:
vo_file = ctx.actions.declare_file(
src.basename.replace(".v", ".vo"),
)
glob_file = ctx.actions.declare_file(
src.basename.replace(".v", ".glob"),
)

# Build coqc arguments
args = ctx.actions.args()
args.add("-q") # Quiet mode

# Add include paths
for inc in include_paths:
args.add("-Q", inc, inc.replace("/", "."))

# Add output directory
args.add("-o", vo_file)

# Add source file
args.add(src)

# Get dependency .vo files
dep_vo_files = []
for dep_depset in transitive_compiled:
dep_vo_files.extend(dep_depset.to_list())

ctx.actions.run(
executable = coqc,
arguments = [args],
inputs = depset([src] + dep_vo_files, transitive = transitive_sources),
outputs = [vo_file, glob_file],
mnemonic = "CoqCompile",
progress_message = "Compiling Coq proof %{input}",
)

compiled_files.append(vo_file)

# Create depsets
source_depset = depset(sources)
compiled_depset = depset(compiled_files)
all_sources = depset(sources, transitive = transitive_sources)

return [
DefaultInfo(
files = compiled_depset,
runfiles = ctx.runfiles(files = sources + compiled_files),
),
RocqInfo(
sources = source_depset,
compiled = compiled_depset,
include_paths = include_paths,
transitive_deps = all_sources,
),
]

def _rocq_library_source_only(ctx, sources):
"""Fallback when no toolchain available - just collect sources."""
transitive_sources = []
include_paths = []

for dep in ctx.attr.deps:
if RocqInfo in dep:
transitive_sources.append(dep[RocqInfo].transitive_deps)
include_paths.extend(dep[RocqInfo].include_paths)

include_paths.append(ctx.attr.include_path if ctx.attr.include_path else ctx.label.package)

source_depset = depset(sources)
all_sources = depset(sources, transitive = transitive_sources)

# Return providers
return [
DefaultInfo(
files = source_depset,
runfiles = ctx.runfiles(files = sources),
),
RocqInfo(
sources = source_depset,
compiled = None,
include_paths = include_paths,
transitive_deps = all_sources,
),
]

# Rocq proof test rule
def _rocq_proof_test_impl(ctx):
"""Check that Rocq proof files are syntactically valid.
"""Test that Rocq proofs compile successfully.

Creates a test script that verifies .v files exist and are readable.
Full proof checking requires Rocq toolchain.
If toolchain available, verifies .vo files were produced.
Otherwise, just checks source files exist.
"""
sources = ctx.files.srcs
dep_sources = []
dep_compiled = []

for dep in ctx.attr.deps:
if RocqInfo in dep:
dep_sources.extend(dep[RocqInfo].transitive_deps.to_list())
if dep[RocqInfo].compiled:
dep_compiled.extend(dep[RocqInfo].compiled.to_list())

all_sources = sources + dep_sources

Expand All @@ -86,7 +174,13 @@ echo "Checking Rocq proof files..."
script_content += 'echo " ✓ {path}"\n'.format(path = src.short_path)
script_content += 'test -f "{path}" || {{ echo "Missing: {path}"; exit 1; }}\n'.format(path = src.short_path)

script_content += 'echo "All {count} proof files present."\n'.format(count = len(sources))
if dep_compiled:
script_content += 'echo "Checking compiled .vo files..."\n'
for vo in dep_compiled:
script_content += 'echo " ✓ {path}"\n'.format(path = vo.short_path)
script_content += 'test -f "{path}" || {{ echo "Missing compiled: {path}"; exit 1; }}\n'.format(path = vo.short_path)

script_content += 'echo "All {count} proof files verified."\n'.format(count = len(sources))

# Write test script
script = ctx.actions.declare_file(ctx.label.name + "_test.sh")
Expand All @@ -99,7 +193,7 @@ echo "Checking Rocq proof files..."
return [
DefaultInfo(
executable = script,
runfiles = ctx.runfiles(files = all_sources),
runfiles = ctx.runfiles(files = all_sources + dep_compiled),
),
]

Expand All @@ -119,7 +213,8 @@ rocq_library = rule(
doc = "Additional include path for this library",
),
},
doc = "Collects Rocq .v files into a library for proof development",
toolchains = ["@rules_rocq_rust//rocq:toolchain_type"],
doc = "Compiles Rocq .v files into a library",
)

rocq_proof_test = rule(
Expand All @@ -135,5 +230,5 @@ rocq_proof_test = rule(
),
},
test = True,
doc = "Verifies Rocq proof files are present and readable",
doc = "Verifies Rocq proof files compile successfully",
)
66 changes: 46 additions & 20 deletions rocq/toolchain.bzl
Original file line number Diff line number Diff line change
@@ -1,38 +1,64 @@
"""Rocq toolchain definitions.

This file provides the rocq_stdlib_filegroup rule for managing Rocq standard library files,
following the exact pattern established by rules_rust.
This file provides toolchain info provider and rules for Rocq/Coq compilation.
"""

load("@bazel_skylib//lib:paths.bzl", "paths")

# Rocq standard library filegroup
# This follows the exact pattern from rules_rust's rust_stdlib_filegroup
def _rocq_stdlib_filegroup_impl(ctx):
"""Implementation for Rocq standard library filegroup."""
# This is a simple filegroup for now
# In a full implementation, this would handle standard library discovery
# and platform-specific libraries
pass

rocq_stdlib_filegroup = rule(
implementation = _rocq_stdlib_filegroup_impl,
# Toolchain info provider - used by rocq_library to find coqc
RocqToolchainInfo = provider(
doc = "Information about the Rocq/Coq toolchain",
fields = {
"coqc": "File for the coqc compiler",
"coq_tools": "depset of all Coq tool binaries",
"stdlib": "depset of standard library .vo files",
},
)

def _rocq_toolchain_info_impl(ctx):
"""Create a RocqToolchainInfo provider for use with Bazel toolchains."""
coqc_files = ctx.files.coqc
coqc = coqc_files[0] if coqc_files else None

toolchain_info = RocqToolchainInfo(
coqc = coqc,
coq_tools = depset(ctx.files.coq_tools),
stdlib = depset(ctx.files.stdlib),
)

return [
platform_common.ToolchainInfo(
rocq_info = toolchain_info,
),
DefaultInfo(
files = depset(ctx.files.coq_tools),
),
]

rocq_toolchain_info = rule(
implementation = _rocq_toolchain_info_impl,
attrs = {
"srcs": attr.label_list(
"coqc": attr.label(
allow_files = True,
doc = "The coqc compiler binary",
),
"coq_tools": attr.label(
allow_files = True,
doc = "Rocq standard library files",
doc = "All Coq tool binaries",
),
"stdlib": attr.label(
allow_files = True,
doc = "Coq standard library files",
),
},
doc = "Creates a filegroup for Rocq standard library files",
doc = "Provides Rocq toolchain information for compilation rules",
)

# Additional toolchain-related functions can be added here
# as the implementation matures

# Rocq standard library filegroup helper
def rocq_stdlib_filegroup(name, srcs = None, **kwargs):
"""Helper function to create standard library filegroups."""
native.filegroup(
name = name,
srcs = srcs,
**kwargs
)
)
37 changes: 15 additions & 22 deletions toolchains/rocq_nix_toolchain.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -51,32 +51,16 @@ def _get_coq_attr(version):

# BUILD file content for the nixpkgs_package
_COQ_BUILD_FILE = '''
load("@rules_rocq_rust//rocq:toolchain.bzl", "rocq_toolchain_info")

package(default_visibility = ["//visibility:public"])

# Coq compiler
# Coq compiler - the main binary we need
filegroup(
name = "coqc",
name = "coqc_bin",
srcs = glob(["bin/coqc"]),
)

# Coq top-level
filegroup(
name = "coqtop",
srcs = glob(["bin/coqtop"]),
)

# Coq documentation generator
filegroup(
name = "coqdoc",
srcs = glob(["bin/coqdoc"]),
)

# Coq checker
filegroup(
name = "coqchk",
srcs = glob(["bin/coqchk"]),
)

# All Coq binaries
filegroup(
name = "coq_tools",
Expand Down Expand Up @@ -104,10 +88,19 @@ filegroup(
srcs = glob(["lib/coq/**/*"]),
)

# Toolchain target
# Toolchain info wrapper - provides the coqc executable
# This is what the toolchain rule expects
rocq_toolchain_info(
name = "rocq_toolchain_info",
coqc = ":coqc_bin",
coq_tools = ":coq_tools",
stdlib = ":stdlib",
)

# Register as toolchain
toolchain(
name = "rocq_toolchain",
toolchain = ":coq_tools",
toolchain = ":rocq_toolchain_info",
toolchain_type = "@rules_rocq_rust//rocq:toolchain_type",
)

Expand Down
Loading