diff --git a/.cirrus.yml b/.cirrus.yml index 021c004..e827a83 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,8 +1,8 @@ container: - image: l.gcr.io/google/bazel:latest + image: gcr.io/bazel-public/bazel:latest task: name: Build the example document - build_script: bazel build //example:all + build_script: bazel build //example:my_report //example:my_dvi_report task: name: Build all package tests build_script: bazel build //packages:all diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..22df597 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,22 @@ +name: Build and Deploy +on: [push] +permissions: + contents: write +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v3 + - uses: mishas/setup-bazelisk-action@v1 + - name: Mount bazel cache # Optional + uses: actions/cache@v1 + with: + path: "~/.cache/bazel" + key: bazel + - name: build + shell: bash + run: > + bazelisk build //example:example_svg; + bazelisk build //...; + diff --git a/WORKSPACE b/WORKSPACE index d26aa7b..9be1a81 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -2,6 +2,15 @@ workspace(name = "bazel_latex") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +http_archive( + name = "platforms", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/platforms/releases/download/0.0.6/platforms-0.0.6.tar.gz", + "https://github.com/bazelbuild/platforms/releases/download/0.0.6/platforms-0.0.6.tar.gz", + ], + sha256 = "5308fc1d8865406a49427ba24a9ab53087f17f5266a7aabbfc28823f3916e1ca", +) + http_archive( name = "bazel_toolchains", sha256 = "109a99384f9d08f9e75136d218ebaebc68cc810c56897aea2224c57932052d30", @@ -22,3 +31,11 @@ load("@bazel_latex//:repositories.bzl", "latex_repositories") latex_repositories() +# Needed for building ghostscript +# Which is needed by dvisvgm, +# dvisvgm is part of the texlive toolchain, +# but cannot produce correct svg files without dynamically +# linking to ghostscript. +load("@rules_foreign_cc//foreign_cc:repositories.bzl", "rules_foreign_cc_dependencies") + +rules_foreign_cc_dependencies() diff --git a/example/BUILD.bazel b/example/BUILD.bazel index 3694b55..09da9b7 100644 --- a/example/BUILD.bazel +++ b/example/BUILD.bazel @@ -1,4 +1,4 @@ -load("@bazel_latex//:latex.bzl", "latex_document") +load("@bazel_latex//:latex.bzl", "latex_document", "latex_to_svg") latex_document( name = "my_report", @@ -26,3 +26,14 @@ latex_document( cmd_flags = ["--bibtex-cmd=biber"], main = "my_report.tex", ) + +latex_document( + name = "my_standalone_figure", + srcs = ["@bazel_latex//packages:drawstack"], + main = "tikz.tex", +) + +latex_to_svg( + name = "example_svg", + src = ":my_standalone_figure", +) diff --git a/example/tikz.tex b/example/tikz.tex new file mode 100644 index 0000000..3a01f46 --- /dev/null +++ b/example/tikz.tex @@ -0,0 +1,12 @@ +\documentclass{article} +\usepackage{tikz} +\begin{document} +\begin{tikzpicture} + +\draw (-2,0) -- (2,0); +\filldraw [gray] (0,0) circle (2pt); +\draw (-2,-2) .. controls (0,0) .. (2,-2); +\draw (-2,2) .. controls (-1,0) and (1,0) .. (2,2); + +\end{tikzpicture} +\end{document} diff --git a/latex.bzl b/latex.bzl index 4d4e811..5fd79fa 100644 --- a/latex.bzl +++ b/latex.bzl @@ -1,3 +1,5 @@ +load("@rules_foreign_cc//foreign_cc:providers.bzl", "ForeignCcArtifactInfo", "ForeignCcDepsInfo") + LatexOutputInfo = provider(fields = ['format', 'file']) def _latex_impl(ctx): @@ -79,8 +81,81 @@ _latex = rule( implementation = _latex_impl, ) -def latex_document(name, main, srcs = [], tags = [], cmd_flags = [], format="pdf"): +def _latex_to_svg_impl(ctx): + toolchain = ctx.toolchains["@bazel_latex//:latex_toolchain_type"].latexinfo + + custom_dependencies = [] + for deps in ctx.attr.deps: + for file in deps.files.to_list(): + if file.dirname not in custom_dependencies: + custom_dependencies.append(file.dirname) + custom_dependencies = ','.join(custom_dependencies) + + src = ctx.attr.src + if LatexOutputInfo in src: + input_file = src[LatexOutputInfo].file + input_format = src[LatexOutputInfo].format + else: + fail("LatexOutputInfo provider not available in src") + + flags = [] + if "pdf" in input_format: + flags.append("--flag=--pdf") + if ctx.attr.box: + flags.append("--flag=-b{}".format(ctx.attr.box)) + artifact = ctx.attr._libgs[ForeignCcDepsInfo].artifacts.to_list()[0] + libgs_path = ctx.attr.libgs_ext.format(artifact.lib_dir_name) + libgs = ["--env=LIBGS" + ":" + ctx.files._libgs[0].dirname + libgs_path] + + ctx.actions.run( + mnemonic = "DviSvgM", + use_default_shell_env = True, + executable = ctx.executable._tool, + arguments = [ + "--dep-tool=" + toolchain.kpsewhich.files.to_list()[0].path, + "--tool=" + toolchain.dvisvgm.files.to_list()[0].path, + "--input=" + input_file.path, + "--output=" + ctx.outputs.out.path, + "--tool-output=" + input_file.basename.rsplit(".", 1)[0] + ".svg", + "--inputs=" + custom_dependencies, + ] + flags + libgs, + inputs = depset( + direct = ctx.files.src + + ctx.files.deps + + [toolchain.dvisvgm.files.to_list()[0]] + + ctx.files._libgs, + transitive = [ + toolchain.kpsewhich.files, + toolchain.dvisvgm.files, + ], + ), + outputs = [ctx.outputs.out], + tools = [ctx.executable._tool], + ) + +_latex_to_svg = rule( + attrs = { + "src": attr.label(), + "deps": attr.label_list(allow_files = True), + "box": attr.string( + default = "", + values = ["", "exact", "min"], + ), + "libgs_ext": attr.string(), + "_libgs": attr.label(default="@bazel_latex//third_party:lib_ghost_script_configure"), + "_tool": attr.label( + default = Label("@bazel_latex//:tool_wrapper_py"), + executable = True, + cfg = "host", + ), + }, + outputs = {"out": "%{name}.svg"}, + toolchains = ["@bazel_latex//:latex_toolchain_type"], + implementation = _latex_to_svg_impl, +) + +def latex_document(name, main, srcs = [], tags = [], cmd_flags = [], format="pdf"): _latex( name = name, srcs = srcs + ["@bazel_latex//:core_dependencies"], @@ -107,3 +182,20 @@ def latex_document(name, main, srcs = [], tags = [], cmd_flags = [], format="pdf args = ["None"], tags = tags, ) + +def latex_to_svg(name, src, deps = [], **kwargs): + + _latex_to_svg( + name = name, + src = src, + deps = deps + [ + "@bazel_latex//:core_dependencies", + "@bazel_latex//third_party:ghostscript_dependencies" + ], + libgs_ext = select({ + "@platforms//os:macos": "/{}/libgs.dylib", + "@platforms//os:windows": "\\{}\\libgs.dll", + "//conditions:default": "/{}/libgs.so", + }), + **kwargs + ) diff --git a/repositories.bzl b/repositories.bzl index 8ae14de..6b855b8 100644 --- a/repositories.bzl +++ b/repositories.bzl @@ -5644,7 +5644,7 @@ def latex_repositories(): name = name, build_file_content = """ exports_files( - ["kpsewhich", "luatex", "bibtex", "biber"], + ["kpsewhich", "luatex", "bibtex", "biber", "dvisvgm"], visibility = ["//visibility:public"], ) """, @@ -5691,7 +5691,30 @@ filegroup( strip_prefix = "latexrun-38ff6ec2815654513c91f64bdf2a5760c85da26e", url = "https://github.com/aclements/latexrun/archive/38ff6ec2815654513c91f64bdf2a5760c85da26e.tar.gz", ) + + http_archive( + name = "rules_foreign_cc", + sha256 = "2a4d07cd64b0719b39a7c12218a3e507672b82a97b98c6a89d38565894cf7c51", + strip_prefix = "rules_foreign_cc-0.9.0", + url = "https://github.com/bazelbuild/rules_foreign_cc/archive/refs/tags/0.9.0.tar.gz", + ) + + _ALL_CONTENT = """\ +filegroup( + name = "all_srcs", + srcs = glob(["**"]), + visibility = ["//visibility:public"], +) +""" + http_archive( + name = "ghost_script_source", + sha256 = "89a4e73be3edc3e40c46892332f29b0f7ed6003b0b952697e708aed2444d9ed1", + strip_prefix = "ghostpdl-ghostpdl-9.26", + build_file_content = _ALL_CONTENT, + url = "https://github.com/ArtifexSoftware/ghostpdl/archive/refs/tags/ghostpdl-9.26.tar.gz", + ) + native.register_toolchains( "@bazel_latex//:latex_toolchain_amd64-freebsd", "@bazel_latex//:latex_toolchain_x86_64-darwin", diff --git a/third_party/BUILD.bazel b/third_party/BUILD.bazel new file mode 100644 index 0000000..cd3b400 --- /dev/null +++ b/third_party/BUILD.bazel @@ -0,0 +1,24 @@ +load("@rules_foreign_cc//foreign_cc:defs.bzl", "configure_make") + +filegroup( + name = "ghostscript_dependencies", + srcs = [ + "@texlive_texmf__texmf-dist__dvips__base", + ], + visibility = ["//visibility:public"], +) + +configure_make( + name = "lib_ghost_script_configure", + configure_in_place = True, + autogen = True, + install_prefix = "output", + lib_source = "@ghost_script_source//:all_srcs", + targets = ["so", "soinstall"], + out_shared_libs = select({ + "@platforms//os:macos": ["libgs.dylib"], + "@platforms//os:windows": ["libgs.dll"], + "//conditions:default": ["libgs.so"], + }), + visibility = ["//visibility:public"] +) diff --git a/toolchain.bzl b/toolchain.bzl index e299e62..d5a2243 100644 --- a/toolchain.bzl +++ b/toolchain.bzl @@ -1,6 +1,6 @@ LatexInfo = provider( doc = "Information about how to invoke the latex compiler", - fields = ["kpsewhich", "luatex", "bibtex", "biber"], + fields = ["kpsewhich", "luatex", "bibtex", "biber", "dvisvgm"], ) def _latex_toolchain_info_impl(ctx): @@ -11,6 +11,7 @@ def _latex_toolchain_info_impl(ctx): luatex = ctx.attr.luatex, bibtex = ctx.attr.bibtex, biber = ctx.attr.biber, + dvisvgm = ctx.attr.dvisvgm, ), ), ] @@ -37,6 +38,11 @@ _latex_toolchain_info = rule( cfg = "host", executable = True, ), + "dvisvgm": attr.label( + allow_single_file = True, + cfg = "host", + executable = True, + ), }, implementation = _latex_toolchain_info_impl, ) @@ -48,6 +54,7 @@ def latex_toolchain(platform, exec_compatible_with): luatex = "@texlive_bin__%s//:luatex" % platform, bibtex = "@texlive_bin__%s//:bibtex" % platform, biber = "@texlive_bin__%s//:biber" % platform, + dvisvgm = "@texlive_bin__%s//:dvisvgm" % platform, visibility = ["//visibility:public"], )