diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index 03be674..0d28519 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -11,6 +11,11 @@ tasks:
- "//..."
test_targets:
- "//..."
+ windows:
+ build_targets:
+ - "//..."
+ test_targets:
+ - "//..."
e2e_ubuntu2204:
platform: ubuntu2204
diff --git a/.bazelrc b/.bazelrc
new file mode 100644
index 0000000..265d4e1
--- /dev/null
+++ b/.bazelrc
@@ -0,0 +1,25 @@
+###############################################################################
+## Bazel Configuration Flags
+##
+## `.bazelrc` is a Bazel configuration file.
+## https://bazel.build/docs/best-practices#bazelrc-file
+###############################################################################
+
+# https://github.com/bazelbuild/bazel/issues/8195
+build --incompatible_disallow_empty_glob=true
+
+# https://github.com/bazelbuild/bazel/issues/12821
+build --nolegacy_external_runfiles
+
+# https://github.com/bazelbuild/bazel/issues/23043.
+build --incompatible_autoload_externally=
+
+###############################################################################
+## Custom user flags
+##
+## This should always be the last thing in the `.bazelrc` file to ensure
+## consistent behavior when setting flags in that file as `.bazelrc` files are
+## evaluated top to bottom.
+###############################################################################
+
+try-import %workspace%/user.bazelrc
diff --git a/.gitignore b/.gitignore
index d97b24c..43b6234 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
/e2e/*/bazel-*
.idea
*.sw*
+user.bazelrc
diff --git a/MODULE.bazel b/MODULE.bazel
index e843949..ba56ce8 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -10,6 +10,7 @@ module(
bazel_dep(name = "platforms", version = "0.0.10")
bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "rules_cc", version = "0.0.17")
+bazel_dep(name = "rules_shell", version = "0.4.0")
repos = use_extension("@rules_perl//perl:extensions.bzl", "perl_repositories")
use_repo(
@@ -35,5 +36,3 @@ use_repo(
"fcgi",
"genhtml",
)
-
-bazel_dep(name = "rules_shell", version = "0.4.0", dev_dependency = True)
diff --git a/examples/genhtml/BUILD b/examples/genhtml/BUILD
index 5f3e221..5535748 100644
--- a/examples/genhtml/BUILD
+++ b/examples/genhtml/BUILD
@@ -14,16 +14,33 @@
load("@rules_perl//perl:perl.bzl", "perl_test")
+genrule(
+ name = "generated_html",
+ srcs = [
+ "coverage.dat",
+ "genhtml_test.t",
+ ],
+ outs = ["index.html"],
+ cmd = "$(execpath @genhtml//:genhtml_bin) --quiet --output-directory $$(dirname $(execpath index.html)) $(execpath coverage.dat)",
+ tools = [
+ "@genhtml//:genhtml_bin",
+ ],
+)
+
perl_test(
name = "genhtml_test",
srcs = ["genhtml_test.t"],
data = [
- "coverage.dat",
- "genhtml_test.t",
- "@genhtml//:genhtml_bin",
+ ":index.html",
],
env = {
- "GENHTML_BIN": "$(rlocationpath @genhtml//:genhtml_bin)",
+ "COVERAGE_INDEX_HTML": "$(rootpath :index.html)",
},
+ # TODO: A runfiles API should be implemented to find the `index.html` file for this test.
+ # For more details see: https://github.com/bazel-contrib/rules_perl/issues/85
+ target_compatible_with = select({
+ "@platforms//os:windows": ["@platforms//:incompatible"],
+ "//conditions:default": [],
+ }),
visibility = ["//visibility:public"],
)
diff --git a/examples/genhtml/genhtml_test.t b/examples/genhtml/genhtml_test.t
index 64f67fd..5e76d01 100644
--- a/examples/genhtml/genhtml_test.t
+++ b/examples/genhtml/genhtml_test.t
@@ -14,8 +14,17 @@
use strict;
use warnings;
+use Test::More;
-use Test::More tests => 1;
+my $file = $ENV{'COVERAGE_INDEX_HTML'};
+ok(defined $file, 'COVERAGE_INDEX_HTML is set');
-`../$ENV{GENHTML_BIN} -o $ENV{TEST_UNDECLARED_OUTPUTS_DIR} examples/genhtml/coverage.dat`;
-ok(-e "$ENV{TEST_UNDECLARED_OUTPUTS_DIR}/index.html", 'genhtml generated index.html');
+if (defined $file) {
+ open my $fh, '<', $file or die "Could not open file '$file': $!\n";
+ my $content = do { local $/; <$fh> };
+ close $fh;
+
+ like($content, qr{
LCOV - coverage\.dat}, 'Expected tag found');
+}
+
+done_testing;
diff --git a/examples/hello_world/BUILD b/examples/hello_world/BUILD
index 15cea2e..d13e77b 100644
--- a/examples/hello_world/BUILD
+++ b/examples/hello_world/BUILD
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-load("//perl:perl.bzl", "perl_binary")
+load("//perl:perl.bzl", "perl_binary", "perl_test")
package(default_visibility = ["//visibility:public"])
@@ -20,3 +20,8 @@ perl_binary(
name = "hello_world",
srcs = ["hello_world.pl"],
)
+
+perl_test(
+ name = "hello_world_test",
+ srcs = ["hello_world_test.t"],
+)
diff --git a/examples/hello_world/hello_world_test.t b/examples/hello_world/hello_world_test.t
new file mode 100644
index 0000000..29292e7
--- /dev/null
+++ b/examples/hello_world/hello_world_test.t
@@ -0,0 +1,5 @@
+use strict;
+use warnings;
+use Test::More tests => 1;
+
+is(1 + 2, 3, '1 + 2 equals 3');
diff --git a/perl/BUILD b/perl/BUILD
index 63b6ae7..62c085f 100644
--- a/perl/BUILD
+++ b/perl/BUILD
@@ -1,7 +1,11 @@
load("//:platforms.bzl", "platforms")
load(":toolchain.bzl", "current_perl_toolchain", "perl_toolchain")
-exports_files(["binary_wrapper.tpl"])
+alias(
+ name = "binary_wrapper.tpl",
+ actual = "//perl/private:binary_wrapper.tpl",
+ visibility = ["//visibility:public"],
+)
# toolchain_type defines a name for a kind of toolchain. Our toolchains
# declare that they have this type. Our rules request a toolchain of this type.
diff --git a/perl/deps.bzl b/perl/deps.bzl
index cd5dd68..d47be7a 100644
--- a/perl/deps.bzl
+++ b/perl/deps.bzl
@@ -48,6 +48,14 @@ def perl_rules_dependencies():
sha256 = "97e70364e9249702246c0e9444bccdc4b847bed1eb03c5a3ece4f83dfe6abc44",
)
+ _maybe(
+ http_archive,
+ name = "rules_shell",
+ sha256 = "3e114424a5c7e4fd43e0133cc6ecdfe54e45ae8affa14fadd839f29901424043",
+ strip_prefix = "rules_shell-0.4.0",
+ url = "https://github.com/bazelbuild/rules_shell/releases/download/v0.4.0/rules_shell-v0.4.0.tar.gz",
+ )
+
def _maybe(rule, name, **kwargs):
"""Declares an external repository if it hasn't been declared already."""
if name not in native.existing_rules():
diff --git a/perl/private/BUILD b/perl/private/BUILD
index 8ab63db..e55528e 100644
--- a/perl/private/BUILD
+++ b/perl/private/BUILD
@@ -1 +1,14 @@
-exports_files(["binary_wrapper.tpl"])
+exports_files([
+ "binary_wrapper.bat.tpl",
+ "binary_wrapper.sh.tpl",
+ "entrypoint.pl",
+])
+
+alias(
+ name = "binary_wrapper.tpl",
+ actual = select({
+ "@platforms//os:windows": "binary_wrapper.bat.tpl",
+ "//conditions:default": "binary_wrapper.sh.tpl",
+ }),
+ visibility = ["//visibility:public"],
+)
diff --git a/perl/private/binary_wrapper.bat.tpl b/perl/private/binary_wrapper.bat.tpl
new file mode 100644
index 0000000..e2a000e
--- /dev/null
+++ b/perl/private/binary_wrapper.bat.tpl
@@ -0,0 +1,76 @@
+@ECHO OFF
+
+SETLOCAL ENABLEEXTENSIONS
+SETLOCAL ENABLEDELAYEDEXPANSION
+
+@REM Usage of rlocation function:
+@REM
+@REM call :rlocation
+@REM
+@REM The rlocation function maps the given to its absolute
+@REM path and stores the result in a variable named . This
+@REM function fails if the doesn't exist in mainifest file.
+:: Start of rlocation
+goto :rlocation_end
+:rlocation
+if "%~2" equ "" (
+ echo>&2 ERROR: Expected two arguments for rlocation function.
+ exit 1
+)
+if exist "%RUNFILES_DIR%" (
+ set RUNFILES_MANIFEST_FILE=%RUNFILES_DIR%_manifest
+)
+if "%RUNFILES_MANIFEST_FILE%" equ "" (
+ set RUNFILES_MANIFEST_FILE=%~f0.runfiles\MANIFEST
+)
+if not exist "%RUNFILES_MANIFEST_FILE%" (
+ set RUNFILES_MANIFEST_FILE=%~f0.runfiles_manifest
+)
+set MF=%RUNFILES_MANIFEST_FILE:/=\%
+if not exist "%MF%" (
+ echo>&2 ERROR: Manifest file %MF% does not exist.
+ exit 1
+)
+set runfile_path=%~1
+for /F "tokens=2* usebackq" %%i in (`%SYSTEMROOT%\system32\findstr.exe /l /c:"!runfile_path! " "%MF%"`) do (
+ set abs_path=%%i
+)
+if "!abs_path!" equ "" (
+ echo>&2 ERROR: !runfile_path! not found in runfiles manifest
+ exit 1
+)
+set %~2=!abs_path!
+exit /b 0
+:rlocation_end
+
+
+@REM Function to replace forward slashes with backslashes.
+goto :slocation_end
+:slocation
+set "input=%~1"
+set "varName=%~2"
+set "output="
+
+@REM Replace forward slashes with backslashes
+set "output=%input:/=\%"
+
+@REM Assign the sanitized path to the specified variable
+set "%varName%=%output%"
+exit /b 0
+:slocation_end
+
+
+call :rlocation "{interpreter}" INTERPRETER
+call :rlocation "{entrypoint}" ENTRYPOINT
+call :rlocation "{config}" CONFIG
+call :rlocation "{main}" MAIN
+
+@REM Unset runfiles dir so windows consistently works with and without it.
+set RUNFILES_DIR=
+
+%INTERPRETER% ^
+ %ENTRYPOINT% ^
+ %CONFIG% ^
+ %MAIN% ^
+ "--" ^
+ %*
diff --git a/perl/private/binary_wrapper.sh.tpl b/perl/private/binary_wrapper.sh.tpl
new file mode 100644
index 0000000..61f0f76
--- /dev/null
+++ b/perl/private/binary_wrapper.sh.tpl
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+# --- begin runfiles.bash initialization v3 ---
+# Copy-pasted from the Bazel Bash runfiles library v3.
+set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash
+# shellcheck disable=SC1090
+source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$0.runfiles/$f" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
+ { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
+# --- end runfiles.bash initialization v3 ---
+
+set -euo pipefail
+
+INTERPRETER="$(rlocation "{interpreter}")"
+ENTRYPOINT="$(rlocation "{entrypoint}")"
+CONFIG="$(rlocation "{config}")"
+MAIN="$(rlocation "{main}")"
+
+runfiles_export_envvars
+
+exec \
+ "${INTERPRETER}" \
+ "${ENTRYPOINT}" \
+ "${CONFIG}" \
+ "${MAIN}" \
+ -- \
+ "$@"
diff --git a/perl/private/binary_wrapper.tpl b/perl/private/binary_wrapper.tpl
deleted file mode 100644
index 16e60b5..0000000
--- a/perl/private/binary_wrapper.tpl
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/sh
-
-if [ -n "${RUNFILES_DIR+x}" ]; then
- PATH_PREFIX=$RUNFILES_DIR/{workspace_name}/
-elif [ -s `dirname $0`/../../MANIFEST ]; then
- PATH_PREFIX=`cd $(dirname $0); pwd`/
-elif [ -d $0.runfiles ]; then
- PATH_PREFIX=`cd $0.runfiles; pwd`/{workspace_name}/
-else
- PATH_PREFIX=./
-fi
-
-export PERL5LIB="$PERL5LIB{PERL5LIB}"
-
-{env_vars} $PATH_PREFIX{interpreter} -I${PATH_PREFIX} ${PATH_PREFIX}{main} "$@"
diff --git a/perl/private/entrypoint.pl b/perl/private/entrypoint.pl
new file mode 100644
index 0000000..c0e8f2a
--- /dev/null
+++ b/perl/private/entrypoint.pl
@@ -0,0 +1,90 @@
+use strict;
+use warnings;
+use File::Spec;
+use File::Temp qw/tempdir/;
+use File::Path qw/make_path/;
+use File::Copy qw/copy/;
+use File::Basename qw/dirname/;
+use Cwd 'abs_path';
+use JSON::PP;
+
+# Ensure enough args
+die "Usage: $0 -- [args...]" unless @ARGV >= 3;
+
+# Extract config path and main script path
+my $config_path = shift @ARGV;
+my $main_path = shift @ARGV;
+
+# Find `--` separator
+my $separator_index = 0;
+$separator_index++ until $separator_index >= @ARGV || $ARGV[$separator_index] eq '--';
+die "Missing -- separator after config and main script paths" if $separator_index == @ARGV;
+
+# Get args after --
+my @extra_args = @ARGV[ $separator_index + 1 .. $#ARGV ];
+splice(@ARGV, $separator_index); # remove args after --
+
+# Load JSON config
+open my $fh, '<', $config_path or die "Can't open config file '$config_path': $!";
+my $json_text = do { local $/; <$fh> };
+close $fh;
+
+my $config = decode_json($json_text);
+my $includes = $config->{includes} // [];
+
+# Create RUNFILES_DIR if not set
+my $runfiles = $ENV{RUNFILES_DIR};
+unless (defined $runfiles) {
+ my $manifest = $ENV{RUNFILES_MANIFEST_FILE}
+ or die "RUNFILES_DIR is not set and RUNFILES_MANIFEST_FILE is not provided.\n";
+
+ # Create a temporary runfiles directory
+ $runfiles = tempdir(CLEANUP => 1);
+ if (defined $ENV{RULES_PERL_DEBUG}) {
+ warn "[DEBUG] RUNFILES_DIR created: $runfiles\n";
+ }
+ $ENV{RUNFILES_DIR} = $runfiles;
+
+ # Copy entries from manifest
+ open my $mfh, '<', $manifest or die "Failed to open manifest file '$manifest': $!";
+ while (my $line = <$mfh>) {
+ chomp $line;
+ next if $line =~ /^\s*$/; # skip blank lines
+
+ my ($rel_path, $real_path) = split ' ', $line, 2;
+ die "Invalid manifest line: $line" unless defined $real_path;
+
+ my $dst_path = File::Spec->catfile($runfiles, $rel_path);
+ make_path(dirname($dst_path));
+ copy($real_path, $dst_path)
+ or die "Failed to copy '$real_path' to '$dst_path': $!";
+ }
+ close $mfh;
+}
+
+# Make sure RUNFILES_DIR is absolute
+unless (File::Spec->file_name_is_absolute($runfiles)) {
+ $runfiles = File::Spec->rel2abs($runfiles);
+ $ENV{RUNFILES_DIR} = $runfiles;
+}
+
+# Build include paths relative to RUNFILES_DIR
+my @include_paths = map { File::Spec->catfile($runfiles, $_) } @$includes;
+
+# Get current Perl interpreter
+my $perl = abs_path($^X);
+
+# Build -I include flags
+my @inc_flags = map { ('-I', $_) } @include_paths;
+
+# Build the full command array
+my @cmd = ($perl, @inc_flags, $main_path, @extra_args);
+
+# Debug output if RULES_PERL_DEBUG is set
+if (defined $ENV{RULES_PERL_DEBUG}) {
+ warn "[DEBUG] Subprocess command: @cmd\n";
+}
+
+# Run the command in a subprocess, exit with its code
+my $exit = system(@cmd);
+exit($exit >> 8);
diff --git a/perl/private/perl.bzl b/perl/private/perl.bzl
index 3e67b23..8a35dc0 100644
--- a/perl/private/perl.bzl
+++ b/perl/private/perl.bzl
@@ -1,5 +1,6 @@
"""Perl rules for Bazel"""
+load("@bazel_skylib//lib:paths.bzl", "paths")
load(":providers.bzl", "PerlInfo")
_PERL_FILE_TYPES = [".pl", ".pm", ".t", ".so", ".ix", ".al", ""]
@@ -35,6 +36,19 @@ _EXECUTABLE_PERL_ATTRS = _COMMON_PERL_ATTRS | {
doc = "The name of the source file that is the main entry point of the application.",
allow_single_file = _PERL_FILE_TYPES,
),
+ "_bash_runfiles": attr.label(
+ cfg = "target",
+ allow_single_file = True,
+ default = Label("@bazel_tools//tools/bash/runfiles"),
+ ),
+ "_entrypoint": attr.label(
+ doc = "The executable entrypoint.",
+ allow_single_file = True,
+ default = Label("//perl/private:entrypoint.pl"),
+ ),
+ "_windows_constraint": attr.label(
+ default = Label("@platforms//os:windows"),
+ ),
"_wrapper_template": attr.label(
allow_single_file = True,
default = Label("//perl/private:binary_wrapper.tpl"),
@@ -82,30 +96,50 @@ def _transitive_deps(ctx, extra_files = [], extra_deps = []):
files = files,
)
-def _include_paths(ctx):
- """Calculate the PERL5LIB paths for a perl_library rule's includes."""
+def _include_paths(ctx, includes, transitive_includes):
+ """Determine the include paths from a target's `includes` attribute.
+
+ Args:
+ ctx (ctx): The rule's context object.
+ includes (list): A list of include paths.
+ transitive_includes (depset): Resolved includes form transitive dependencies.
+
+ Returns:
+ depset: A set of the resolved include paths.
+ """
workspace_name = ctx.label.workspace_name
- if workspace_name:
- workspace_root = "../" + workspace_name
- else:
- workspace_root = ""
- package_root = (workspace_root + "/" + ctx.label.package).strip("/") or "."
- include_paths = [package_root] if "." in ctx.attr.includes else []
- include_paths.extend([package_root + "/" + include for include in ctx.attr.includes if include != "."])
- for dep in ctx.attr.deps:
- include_paths.extend(dep[PerlInfo].includes)
- include_paths = depset(direct = include_paths).to_list()
- return include_paths
+ if not workspace_name:
+ workspace_name = ctx.workspace_name
+
+ include_root = "{}/{}".format(workspace_name, ctx.label.package).rstrip("/")
+
+ result = [workspace_name]
+ for include_str in includes:
+ include_str = ctx.expand_make_variables("includes", include_str, {})
+ if include_str.startswith("/"):
+ continue
+
+ # To prevent "escaping" out of the runfiles tree, we normalize
+ # the path and ensure it doesn't have up-level references.
+ include_path = paths.normalize("{}/{}".format(include_root, include_str))
+ if include_path.startswith("../") or include_path == "..":
+ fail("Path '{}' references a path above the execution root".format(
+ include_str,
+ ))
+ result.append(include_path)
+
+ return depset(result, transitive = [transitive_includes])
def _perl_library_implementation(ctx):
transitive_sources = _transitive_deps(ctx)
+ transitive_includes = depset(transitive = [dep[PerlInfo].includes for dep in ctx.attr.deps])
return [
DefaultInfo(
runfiles = transitive_sources.files,
),
PerlInfo(
transitive_perl_sources = transitive_sources.srcs,
- includes = _include_paths(ctx),
+ includes = _include_paths(ctx, ctx.attr.includes, transitive_includes),
),
]
@@ -121,68 +155,81 @@ def _get_main_from_sources(ctx):
fail("Cannot infer main from multiple 'srcs'. Please specify 'main' attribute.", "main")
return sources[0]
-def _is_identifier(name):
- # Must be non-empty.
- if name == None or len(name) == 0:
- return False
-
- # Must start with alpha or '_'
- if not (name[0].isalpha() or name[0] == "_"):
- return False
-
- # Must consist of alnum characters or '_'s.
- for c in name.elems():
- if not (c.isalnum() or c == "_"):
- return False
- return True
-
def _env_vars(ctx):
- environment = ""
+ environment = {}
for name, value in ctx.attr.env.items():
- if not _is_identifier(name):
- fail("%s is not a valid environment variable name." % str(name))
- value = ctx.expand_location(value, targets = ctx.attr.data)
- environment += ("{key}='{value}' ").format(
- key = name,
- value = value.replace("'", "\\'"),
- )
+ environment[name] = ctx.expand_location(value, targets = ctx.attr.data)
return environment
+def _rlocationpath(file, workspace_name):
+ if file.short_path.startswith("../"):
+ return file.short_path[len("../"):]
+
+ return "{}/{}".format(workspace_name, file.short_path)
+
def _perl_binary_implementation(ctx):
toolchain = ctx.toolchains["@rules_perl//perl:toolchain_type"].perl_runtime
interpreter = toolchain.interpreter
- transitive_sources = _transitive_deps(
- ctx,
- extra_files = toolchain.runtime + [ctx.outputs.executable],
- )
-
main = ctx.file.main
if main == None:
main = _get_main_from_sources(ctx)
- include_paths = []
- for dep in ctx.attr.deps:
- include_paths.extend(dep[PerlInfo].includes)
- perl5lib = ":" + ":".join(include_paths) if include_paths else ""
+ extension = ""
+ workspace_name = ctx.label.workspace_name
+ if not workspace_name:
+ workspace_name = ctx.workspace_name
+ if not workspace_name:
+ workspace_name = "_main"
+
+ include_paths = depset([workspace_name], transitive = [dep[PerlInfo].includes for dep in ctx.attr.deps])
+
+ is_windows = ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo])
+ if is_windows:
+ extension = ".bat"
+
+ output = ctx.actions.declare_file("{}{}".format(ctx.label.name, extension))
+ config = ctx.actions.declare_file("{}.config.json".format(ctx.label.name))
+ transitive_sources = _transitive_deps(ctx, extra_files = toolchain.runtime.to_list() + [
+ ctx.file._bash_runfiles,
+ ctx.file._entrypoint,
+ output,
+ config,
+ ])
+
+ ctx.actions.write(
+ output = config,
+ content = json.encode_indent({
+ "includes": include_paths.to_list(),
+ "runfiles": [
+ _rlocationpath(src, ctx.workspace_name)
+ for src in depset(transitive = [transitive_sources.srcs, toolchain.runtime]).to_list()
+ ],
+ }),
+ )
ctx.actions.expand_template(
template = ctx.file._wrapper_template,
- output = ctx.outputs.executable,
+ output = output,
substitutions = {
- "{PERL5LIB}": perl5lib,
- "{env_vars}": _env_vars(ctx),
- "{interpreter}": interpreter.short_path,
- "{main}": main.short_path,
- "{workspace_name}": ctx.label.workspace_name or ctx.workspace_name,
+ "{config}": _rlocationpath(config, ctx.workspace_name),
+ "{entrypoint}": _rlocationpath(ctx.file._entrypoint, ctx.workspace_name),
+ "{interpreter}": _rlocationpath(interpreter, ctx.workspace_name),
+ "{main}": _rlocationpath(main, ctx.workspace_name),
},
is_executable = True,
)
- return DefaultInfo(
- executable = ctx.outputs.executable,
- runfiles = transitive_sources.files,
- )
+ return [
+ DefaultInfo(
+ executable = output,
+ files = depset([output]),
+ runfiles = transitive_sources.files,
+ ),
+ RunEnvironmentInfo(
+ environment = _env_vars(ctx),
+ ),
+ ]
def _perl_test_implementation(ctx):
return _perl_binary_implementation(ctx)
diff --git a/perl/private/perl_xs.bzl b/perl/private/perl_xs.bzl
index ffa9c5d..12dde5f 100644
--- a/perl/private/perl_xs.bzl
+++ b/perl/private/perl_xs.bzl
@@ -68,7 +68,7 @@ def _perl_xs_implementation(ctx):
toolchain = ctx.toolchains["@rules_perl//perl:toolchain_type"].perl_runtime
xsubpp = toolchain.xsubpp
- toolchain_files = depset(toolchain.runtime)
+ toolchain_files = toolchain.runtime
gen = []
cc_infos = []
diff --git a/perl/toolchain.bzl b/perl/toolchain.bzl
index 2118d36..208b59f 100644
--- a/perl/toolchain.bzl
+++ b/perl/toolchain.bzl
@@ -7,38 +7,54 @@ generated in perl_download in repo.bzl.
PerlRuntimeInfo = provider(
doc = "Information about a Perl interpreter, related commands and libraries",
fields = {
- "interpreter": "A label which points to the Perl interpreter",
- "perlopt": "A list of strings which should be passed to the interpreter",
- "runtime": "A list of labels which points to runtime libraries",
- "xs_headers": "The c library support code for xs modules",
- "xsubpp": "A label which points to the xsubpp command",
+ "interpreter": "File: A label which points to the Perl interpreter",
+ "perlopt": "list[str]: A list of strings which should be passed to the interpreter",
+ "runtime": "depset[File]: A list of labels which points to runtime libraries",
+ "xs_headers": "depset[File]: The c library support code for xs modules",
+ "xsubpp": "File: A label which points to the xsubpp command",
},
)
-def _find_tool(ctx, name):
- cmd = None
- for f in ctx.files.runtime:
- if f.path.endswith("/bin/%s" % name) or f.path.endswith("/bin/%s.exe" % name) or f.path.endswith("/bin/%s.bat" % name):
- cmd = f
- break
- if not cmd:
- fail("could not locate perl tool `%s`" % name)
-
- return cmd
-
-def _find_xs_headers(ctx):
- hdrs = [
- f
- for f in ctx.files.runtime
- if "CORE" in f.path and f.path.endswith(".h")
- ]
- return depset(hdrs)
+def _is_tool(src, name):
+ endings = (
+ "/bin/%s" % name,
+ "/bin/%s.exe" % name,
+ "/bin/%s.bat" % name,
+ )
+ if src.path.endswith(endings):
+ return True
+
+ return False
+
+def _is_xs_header(src):
+ if "CORE" in src.path and src.path.endswith(".h"):
+ return True
+
+ return False
def _perl_toolchain_impl(ctx):
# Find important files and paths.
- interpreter_cmd = _find_tool(ctx, "perl")
- xsubpp_cmd = _find_tool(ctx, "xsubpp")
- xs_headers = _find_xs_headers(ctx)
+ interpreter_cmd = None
+ xsubpp_cmd = None
+ xs_headers = []
+ for file in ctx.files.runtime:
+ if interpreter_cmd == None and _is_tool(file, "perl"):
+ interpreter_cmd = file
+ continue
+
+ if xsubpp_cmd == None and _is_tool(file, "xsubpp"):
+ xsubpp_cmd = file
+ continue
+
+ if _is_xs_header(file):
+ xs_headers.append(file)
+ continue
+
+ if interpreter_cmd == None:
+ fail("Failed to find perl interpreter.")
+
+ if xsubpp_cmd == None:
+ fail("Failed to find perl xsubpp.")
interpreter_cmd_path = interpreter_cmd.path
if ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]):
@@ -50,8 +66,8 @@ def _perl_toolchain_impl(ctx):
perl_runtime = PerlRuntimeInfo(
interpreter = interpreter_cmd,
xsubpp = xsubpp_cmd,
- xs_headers = xs_headers,
- runtime = ctx.files.runtime,
+ xs_headers = depset(xs_headers),
+ runtime = depset(ctx.files.runtime),
perlopt = ctx.attr.perlopt,
),
make_variables = platform_common.TemplateVariableInfo({
@@ -84,9 +100,10 @@ def _current_perl_toolchain_impl(ctx):
toolchain.make_variables,
DefaultInfo(
runfiles = ctx.runfiles(
- files = toolchain.perl_runtime.runtime,
+ [],
+ transitive_files = toolchain.perl_runtime.runtime,
),
- files = depset(toolchain.perl_runtime.runtime),
+ files = toolchain.perl_runtime.runtime,
),
]
diff --git a/test/perl_rule_test.bzl b/test/perl_rule_test.bzl
index 6d2dc9e..49f9630 100644
--- a/test/perl_rule_test.bzl
+++ b/test/perl_rule_test.bzl
@@ -26,7 +26,10 @@ def _perl_library_test(package):
def _perl_binary_test(package):
rule_test(
name = "hello_world_rule_test",
- generates = ["hello_world"],
+ generates = select({
+ "@platforms//os:windows": ["hello_world.bat"],
+ "//conditions:default": ["hello_world"],
+ }),
rule = package + "/hello_world:hello_world",
)
@@ -34,7 +37,10 @@ def _perl_test_test(package):
"""Issue rule tests for perl_test."""
rule_test(
name = "fibonacci_rule_test",
- generates = ["fibonacci_test"],
+ generates = select({
+ "@platforms//os:windows": ["fibonacci_test.bat"],
+ "//conditions:default": ["fibonacci_test"],
+ }),
rule = package + "/fibonacci:fibonacci_test",
)