Update some Python machinery, create sass tools

This commit is contained in:
Reid 'arrdem' McKenzie 2023-05-19 00:51:18 -06:00
parent 94f7b5df2a
commit 4db81ff74a
7 changed files with 335 additions and 1 deletions

View file

@ -41,7 +41,7 @@ git_repository(
name = "rules_python",
remote = "https://github.com/bazelbuild/rules_python.git",
# tag = "0.4.0",
commit = "f0efec5cf8c0ae16483ee677a09ec70737a01bf5",
commit = "693a1587baf055979493565933f8f40225c00c6d",
)
register_toolchains("//tools/python:python3_toolchain")

View file

@ -10,6 +10,12 @@ load("//tools/python:defs.bzl",
"py_project",
)
load("//tools/sass:defs.bzl",
"multi_sass_binary",
"sass_binary",
"sass_library",
)
load("@arrdem_source_pypi//:requirements.bzl",
py_requirement="requirement"
)

View file

@ -158,6 +158,7 @@ py_resources = rule(
def py_project(name=None,
main=None,
main_deps=None,
main_data=None,
shebang=None,
lib_srcs=None,
lib_deps=None,
@ -218,6 +219,7 @@ def py_project(name=None,
name=name,
main=main,
deps=(main_deps or []) + [lib_name],
data=(main_data or []),
imports=[
"src/python",
"src/resources",

View file

@ -17,6 +17,7 @@ icmplib
isort
jinja2
lark
libsass
livereload
lxml
markdown

View file

@ -39,6 +39,7 @@ jsonschema==4.17.3
jsonschema-spec==0.1.4
lark==1.1.5
lazy-object-proxy==1.9.0
libsass==0.22.0
livereload==2.6.3
lxml==4.9.2
Markdown==3.4.3

7
tools/sass/BUILD Normal file
View file

@ -0,0 +1,7 @@
load("@arrdem_source_pypi//:requirements.bzl", "entry_point")
alias(
name = "sassc",
actual = entry_point("libsass", "pysassc"),
visibility = ["//visibility:public"],
)

317
tools/sass/defs.bzl Normal file
View file

@ -0,0 +1,317 @@
# Copyright 2018 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"Compile Sass files to CSS"
_ALLOWED_SRC_FILE_EXTENSIONS = [".sass", ".scss", ".css", ".svg", ".png", ".gif", ".cur", ".jpg", ".webp"]
# Documentation for switching which compiler is used
_COMPILER_ATTR_DOC = """Choose which Sass compiler binary to use.
By default, we use the JavaScript-transpiled version of the
dart-sass library, based on https://github.com/sass/dart-sass.
This is the canonical compiler under active development by the Sass team.
This compiler is convenient for frontend developers since it's released
as JavaScript and can run natively in NodeJS without being locally built.
While the compiler can be configured, there are no other implementations
explicitly supported at this time. In the future, there will be an option
to run Dart Sass natively in the Dart VM. This option depends on the Bazel
rules for Dart, which are currently not actively maintained (see
https://github.com/dart-lang/rules_dart).
"""
SassInfo = provider(
doc = "Collects files from sass_library for use in downstream sass_binary",
fields = {
"transitive_sources": "Sass sources for this target and its dependencies",
},
)
def _collect_transitive_sources(srcs, deps):
"Sass compilation requires all transitive .sass source files"
return depset(
srcs,
transitive = [dep[SassInfo].transitive_sources for dep in deps],
# Provide .sass sources from dependencies first
order = "postorder",
)
def _sass_library_impl(ctx):
"""sass_library collects all transitive sources for given srcs and deps.
It doesn't execute any actions.
Args:
ctx: The Bazel build context
Returns:
The sass_library rule.
"""
transitive_sources = _collect_transitive_sources(
ctx.files.srcs,
ctx.attr.deps,
)
return [
SassInfo(transitive_sources = transitive_sources),
DefaultInfo(
files = transitive_sources,
runfiles = ctx.runfiles(transitive_files = transitive_sources),
),
]
def _run_sass(ctx, input, css_output, map_output = None):
"""run_sass performs an action to compile a single Sass file into CSS."""
# The Sass CLI expects inputs like
# sass <flags> <input_filename> <output_filename>
args = ctx.actions.args()
# Flags (see https://github.com/sass/dart-sass/blob/master/lib/src/executable/options.dart)
args.add_joined(["--style", ctx.attr.output_style], join_with = "=")
if ctx.attr.sourcemap:
args.add("--sourcemap")
if ctx.attr.sourcemap_embed_sources:
args.add("--sourcemap-contents")
# Sources for compilation may exist in the source tree, in bazel-bin, or bazel-genfiles.
for prefix in [".", ctx.var["BINDIR"], ctx.var["GENDIR"]]:
args.add("--include-path=%s/" % prefix)
for include_path in ctx.attr.include_paths:
args.add("--include-path=%s/%s" % (prefix, include_path))
# Last arguments are input and output paths
# Note that the sourcemap is implicitly written to a path the same as the
# css with the added .map extension.
args.add_all([input.path, css_output.path])
ctx.actions.run(
mnemonic = "SassCompiler",
executable = ctx.executable.compiler,
inputs = _collect_transitive_sources([input], ctx.attr.deps),
tools = [ctx.executable.compiler],
arguments = [args],
outputs = [css_output, map_output] if map_output else [css_output],
use_default_shell_env = True,
)
def _sass_binary_impl(ctx):
# Make sure the output CSS is available in runfiles if used as a data dep.
if ctx.attr.sourcemap:
map_file = ctx.outputs.map_file
outputs = [ctx.outputs.css_file, map_file]
else:
map_file = None
outputs = [ctx.outputs.css_file]
_run_sass(ctx, ctx.file.src, ctx.outputs.css_file, map_file)
return DefaultInfo(runfiles = ctx.runfiles(files = outputs))
def _sass_binary_outputs(src, output_name, output_dir, sourcemap):
"""Get map of sass_binary outputs, including generated css and sourcemaps.
Note that the arguments to this function are named after attributes on the rule.
Args:
src: The rule's `src` attribute
output_name: The rule's `output_name` attribute
output_dir: The rule's `output_dir` attribute
sourcemap: The rule's `sourcemap` attribute
Returns:
Outputs for the sass_binary
"""
output_name = output_name or _strip_extension(src.name) + ".css"
css_file = "/".join([p for p in [output_dir, output_name] if p])
outputs = {
"css_file": css_file,
}
if sourcemap:
outputs["map_file"] = "%s.map" % css_file
return outputs
def _strip_extension(path):
"""Removes the final extension from a path."""
components = path.split(".")
components.pop()
return ".".join(components)
sass_deps_attr = attr.label_list(
doc = "sass_library targets to include in the compilation",
providers = [SassInfo],
allow_files = False,
)
_sass_library_attrs = {
"srcs": attr.label_list(
doc = "Sass source files",
allow_files = _ALLOWED_SRC_FILE_EXTENSIONS,
allow_empty = False,
mandatory = True,
),
"deps": sass_deps_attr,
}
sass_library = rule(
implementation = _sass_library_impl,
attrs = _sass_library_attrs,
)
"""Defines a group of Sass include files.
"""
_sass_binary_attrs = {
"src": attr.label(
doc = "Sass entrypoint file",
mandatory = True,
allow_single_file = _ALLOWED_SRC_FILE_EXTENSIONS,
),
"sourcemap": attr.bool(
default = True,
doc = "Whether source maps should be emitted.",
),
"sourcemap_embed_sources": attr.bool(
default = False,
doc = "Whether to embed source file contents in source maps.",
),
"include_paths": attr.string_list(
doc = "Additional directories to search when resolving imports",
),
"output_dir": attr.string(
doc = "Output directory, relative to this package.",
default = "",
),
"output_name": attr.string(
doc = """Name of the output file, including the .css extension.
By default, this is based on the `src` attribute: if `styles.scss` is
the `src` then the output file is `styles.css.`.
You can override this to be any other name.
Note that some tooling may assume that the output name is derived from
the input name, so use this attribute with caution.""",
default = "",
),
"output_style": attr.string(
doc = "How to style the compiled CSS",
default = "compressed",
values = [
"expanded",
"compressed",
],
),
"deps": sass_deps_attr,
"compiler": attr.label(
doc = _COMPILER_ATTR_DOC,
default = Label(":sassc"),
executable = True,
allow_files = True,
cfg = "host",
),
}
sass_binary = rule(
implementation = _sass_binary_impl,
attrs = _sass_binary_attrs,
outputs = _sass_binary_outputs,
)
def _multi_sass_binary_impl(ctx):
"""multi_sass_binary accepts a list of sources and compile all in one pass.
Args:
ctx: The Bazel build context
Returns:
The multi_sass_binary rule.
"""
inputs = ctx.files.srcs
outputs = []
# Every non-partial Sass file will produce one CSS output file and,
# optionally, one sourcemap file.
for f in inputs:
# Sass partial files (prefixed with an underscore) do not produce any
# outputs.
if f.basename.startswith("_"):
continue
name = _strip_extension(f.basename)
outputs.append(ctx.actions.declare_file(
name + ".css",
sibling = f,
))
if ctx.attr.sourcemap:
outputs.append(ctx.actions.declare_file(
name + ".css.map",
sibling = f,
))
# Use the package directory as the compilation root given to the Sass compiler
root_dir = (ctx.label.workspace_root + "/" if ctx.label.workspace_root else "") + ctx.label.package
# Declare arguments passed through to the Sass compiler.
# Start with flags and then expected program arguments.
args = ctx.actions.args()
args.add("--style", ctx.attr.output_style)
args.add("--load-path", root_dir)
if not ctx.attr.sourcemap:
args.add("--no-source-map")
args.add(root_dir + ":" + ctx.bin_dir.path + '/' + root_dir)
args.use_param_file("@%s", use_always = True)
args.set_param_file_format("multiline")
if inputs:
ctx.actions.run(
inputs = inputs,
outputs = outputs,
executable = ctx.executable.compiler,
arguments = [args],
mnemonic = "SassCompiler",
progress_message = "Compiling Sass",
)
return [DefaultInfo(files = depset(outputs))]
multi_sass_binary = rule(
implementation = _multi_sass_binary_impl,
attrs = {
"srcs": attr.label_list(
doc = "A list of Sass files and associated assets to compile",
allow_files = _ALLOWED_SRC_FILE_EXTENSIONS,
allow_empty = True,
mandatory = True,
),
"sourcemap": attr.bool(
doc = "Whether sourcemaps should be emitted",
default = True,
),
"output_style": attr.string(
doc = "How to style the compiled CSS",
default = "compressed",
values = [
"expanded",
"compressed",
],
),
"compiler": attr.label(
doc = _COMPILER_ATTR_DOC,
default = Label(":sassc"),
executable = True,
cfg = "host",
),
}
)