Skip to content
Merged
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
154 changes: 66 additions & 88 deletions rocq/private/rocq.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -9,123 +9,99 @@ load("@bazel_skylib//lib:paths.bzl", "paths")
RocqInfo = provider(
doc = "Information about Rocq compilation results",
fields = {
"compiled_objects": "depset of .vo files",
"sources": "depset of .v source 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):
"""Compile .v files to .vo using Rocq with proper dependency management.

Follows rules_rust patterns:
- Use depsets for transitive dependencies
- Explicit inputs/outputs for hermetic builds
- ctx.actions.args() for command-line construction
"""Collect .v files for Rocq proof library.

Currently collects source files for later compilation with Rocq/Coq.
Full compilation support requires toolchain configuration.
"""

# Validate source files
sources = ctx.files.srcs
if not sources:
fail("rocq_library requires at least one source file")

# Validate file extensions
for src in sources:
if not src.path.endswith(".v"):
fail("rocq_library only accepts .v files, got: " + src.path)

# Check for rocq binary
if not hasattr(ctx, "executable") or not hasattr(ctx.executable, "_rocq_binary"):
fail("rocq_library requires Rocq toolchain to be configured")

# Process dependencies to get transitive include paths and .vo files
transitive_deps = depset()

# Process dependencies to get transitive sources
transitive_sources = []
include_paths = []

for dep in ctx.attr.deps:
if hasattr(dep, "rocq"):
transitive_deps = depset.union(transitive_deps, dep.rocq.transitive_deps)
include_paths.extend(dep.rocq.include_paths)
if RocqInfo in dep:
transitive_sources.append(dep[RocqInfo].transitive_deps)
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 ".")

# Compile each .v file to .vo
compiled_objects = depset()

for src in sources:
if src.path.endswith(".v"):
output = src.basename + ".vo"
output_file = ctx.actions.declare_file(output)

# Use ctx.actions.run for hermetic execution
ctx.actions.run(
inputs = depset([src]) + transitive_deps,
outputs = [output_file],
executable = ctx.executable._rocq_binary,
arguments = [
"--compile",
src.path,
"--output",
output_file.path,
] + ["--include-paths", ",".join(include_paths)] if include_paths else [],
env = {
"ROCQ_LIBRARY_PATH": ":".join(include_paths),
} if include_paths else {},
)

compiled_objects = depset.union(compiled_objects, depset([output_file]))

# Return providers following rules_rust pattern
include_paths.append(ctx.attr.include_path if ctx.attr.include_path else ctx.label.package)

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

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

# Rocq proof test rule
def _rocq_proof_test_impl(ctx):
"""Run Rocq in proof-checking mode.

Fails the build if proofs don't complete or Qed.
"""Check that Rocq proof files are syntactically valid.

Creates a test script that verifies .v files exist and are readable.
Full proof checking requires Rocq toolchain.
"""

sources = ctx.files.srcs
if not sources:
return []

# Collect dependencies
transitive_deps = depset()
include_paths = []

dep_sources = []

for dep in ctx.attr.deps:
if hasattr(dep, "rocq"):
transitive_deps = depset.union(transitive_deps, dep.rocq.transitive_deps)
include_paths.extend(dep.rocq.include_paths)

# Run proof checking
ctx.actions.run(
inputs = depset(sources) + transitive_deps,
outputs = [], # Proof checking doesn't produce files, just validation
executable = ctx.executable._rocq_binary,
arguments = [
"--proof-check",
"--fail-on-incomplete",
] + [src.path for src in sources] +
["--include-paths", ",".join(include_paths)] if include_paths else [],
env = {
"ROCQ_LIBRARY_PATH": ":".join(include_paths),
} if include_paths else {},
if RocqInfo in dep:
dep_sources.extend(dep[RocqInfo].transitive_deps.to_list())

all_sources = sources + dep_sources

# Create test script
script_content = """#!/bin/bash
set -e
echo "Checking Rocq proof files..."
"""
for src in sources:
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))

# Write test script
script = ctx.actions.declare_file(ctx.label.name + "_test.sh")
ctx.actions.write(
output = script,
content = script_content,
is_executable = True,
)

return [DefaultInfo()]

return [
DefaultInfo(
executable = script,
runfiles = ctx.runfiles(files = all_sources),
),
]

# Rule definitions
rocq_library = rule(
Expand All @@ -136,13 +112,14 @@ rocq_library = rule(
doc = "Rocq source files (.v)",
),
"deps": attr.label_list(
providers = [[RocqInfo]],
doc = "Dependencies on other Rocq libraries",
),
"include_path": attr.string(
doc = "Additional include path for this library",
),
},
doc = "Compiles Rocq .v files into .vo files with proper dependency management",
doc = "Collects Rocq .v files into a library for proof development",
)

rocq_proof_test = rule(
Expand All @@ -153,9 +130,10 @@ rocq_proof_test = rule(
doc = "Rocq proof files to check",
),
"deps": attr.label_list(
providers = [[RocqInfo]],
doc = "Dependencies on other Rocq libraries",
),
},
test = True,
doc = "Runs Rocq in proof-checking mode, fails build if proofs don't complete",
)
doc = "Verifies Rocq proof files are present and readable",
)
Loading