From 96e12c8a274ea3e08648116f3fb052e3de005560 Mon Sep 17 00:00:00 2001 From: Matthew Sotoudeh Date: Mon, 13 Apr 2020 08:40:01 -0700 Subject: Initial code release --- .gitignore | 3 + BUILD.bazel | 7 ++ LICENSE | 230 ++++++++++++++++++++++++++++++++++++++++++ WORKSPACE | 5 + get_arxivable.sh | 12 +++ get_pdf.sh | 5 + latex.bzl | 76 ++++++++++++++ patches/latexrun-force-colors | 28 +++++ patches/latexrun-pull-21 | 76 ++++++++++++++ patches/latexrun-pull-47 | 22 ++++ patches/latexrun-pull-61 | 18 ++++ patches/latexrun-pull-62 | 31 ++++++ pdfcrop.bzl | 52 ++++++++++ pdfcrop.sh | 14 +++ repositories.bzl | 35 +++++++ run_latex.py | 43 ++++++++ setup_texlive.sh | 49 +++++++++ view_pdf.sh | 12 +++ 18 files changed, 718 insertions(+) create mode 100644 .gitignore create mode 100644 BUILD.bazel create mode 100644 LICENSE create mode 100644 WORKSPACE create mode 100755 get_arxivable.sh create mode 100755 get_pdf.sh create mode 100644 latex.bzl create mode 100644 patches/latexrun-force-colors create mode 100644 patches/latexrun-pull-21 create mode 100644 patches/latexrun-pull-47 create mode 100644 patches/latexrun-pull-61 create mode 100644 patches/latexrun-pull-62 create mode 100644 pdfcrop.bzl create mode 100644 pdfcrop.sh create mode 100644 repositories.bzl create mode 100644 run_latex.py create mode 100755 setup_texlive.sh create mode 100755 view_pdf.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b545abe --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.*.swp +.nfs* +/bazel-* diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..f77cc29 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,7 @@ +exports_files([ + "run_latex.py", + "view_pdf.sh", + "get_pdf.sh", + "get_arxivable.sh", + "pdfcrop.sh", +]) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..95a38eb --- /dev/null +++ b/LICENSE @@ -0,0 +1,230 @@ + PLEASE NOTE + +The original ProdriveTechnologies Bazel-LaTeX project was licensed under the +Apache 2.0 license, copied below. To the fullest extent possible, any code in +this repository written by members of the Davis Automated Reasoning Group is +licensed under the MIT (Expat) license, also copied below. + + The MIT License (MIT) + +Copyright (c) 2020 Davis Automated Reasoning Group + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..5491eda --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,5 @@ +workspace(name = "bazel_latex") + +load("@bazel_latex//:repositories.bzl", "latex_repositories") + +latex_repositories() diff --git a/get_arxivable.sh b/get_arxivable.sh new file mode 100755 index 0000000..dc359ba --- /dev/null +++ b/get_arxivable.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +out_name=$(basename $0).tar.gz +build_dir=$BUILD_WORKING_DIRECTORY +paper_dir=$(mktemp -d) + +cp -LRr * $paper_dir +rm $paper_dir/$(basename $0) +python3 external/arxiv_latex_cleaner/arxiv_latex_cleaner.py $paper_dir + +cd $(echo $paper_dir)_arXiv +tar -czvf $build_dir/$out_name * diff --git a/get_pdf.sh b/get_pdf.sh new file mode 100755 index 0000000..7a0909f --- /dev/null +++ b/get_pdf.sh @@ -0,0 +1,5 @@ +#!/bin/sh +filename="$(find . -name '*.pdf')" +builddir=$BUILD_WORKING_DIRECTORY +cp $filename $builddir/$filename +chmod -x+w $builddir/$filename diff --git a/latex.bzl b/latex.bzl new file mode 100644 index 0000000..dc79e4e --- /dev/null +++ b/latex.bzl @@ -0,0 +1,76 @@ +"""Defines latex_document(...) macro. +""" + +def _latex_pdf_impl(ctx): + """Rule to build a single PDF file. + """ + texlive_path = ctx.var.get("TEXLIVE_FULL_DIR", None) + if texlive_path == None: + fail("Please run setup_texlive.sh to set TEXLIVE_FULL_DIR.") + ctx.actions.run( + mnemonic = "LaTeX", + executable = "python", + use_default_shell_env = True, + arguments = [ + ctx.files._run_script[0].path, + texlive_path, + ctx.files._latexrun[0].path, + ctx.label.name, + ctx.files.main[0].path, + ctx.outputs.out.path, + ] + [src.path for src in ctx.files.srcs], + inputs = depset( + direct = (ctx.files.main + ctx.files.srcs + ctx.files._latexrun + + ctx.files._run_script), + ), + outputs = [ctx.outputs.out], + ) + +_latex_pdf = rule( + attrs = { + "main": attr.label(allow_files = True), + "srcs": attr.label_list(allow_files = True), + "_latexrun": attr.label( + allow_files = True, + default = "@bazel_latex_latexrun//:latexrun", + ), + "_run_script": attr.label( + allow_files = True, + default = "@bazel_latex//:run_latex.py", + ), + }, + outputs = {"out": "%{name}.pdf"}, + implementation = _latex_pdf_impl, +) + +def latex_document(name, main, srcs = []): + """Given a TeX file, add rules for compiling and archiving it. + """ + + # PDF generation. + _latex_pdf( + name = name, + srcs = srcs, + main = main, + ) + + # Convenience rule for viewing PDFs. + native.sh_binary( + name = name + "_view", + srcs = ["@bazel_latex//:view_pdf.sh"], + data = [name + ".pdf"], + ) + + # Copy the PDF into the main working directory. + native.sh_binary( + name = name + "_getpdf", + srcs = ["@bazel_latex//:get_pdf.sh"], + data = [name + ".pdf"], + ) + + # Create an arXiv-ready version of the source. + native.sh_binary( + name = name + "_arxivable", + srcs = ["@bazel_latex//:get_arxivable.sh"], + data = srcs + ["@arxiv_latex_cleaner//:all"], + ) diff --git a/patches/latexrun-force-colors b/patches/latexrun-force-colors new file mode 100644 index 0000000..829a1fe --- /dev/null +++ b/patches/latexrun-force-colors @@ -0,0 +1,28 @@ +--- latexrun ++++ latexrun +@@ -416,7 +416,24 @@ class _Terminfo: + else: + s = self.__ensure(cap) + sys.stdout.buffer.write(s) +-terminfo = _Terminfo() ++ ++class _BatchColorTerminfo: ++ def has(self, *caps): ++ for cap in caps: ++ if cap not in ['bold', 'setaf', 'sgr0']: ++ return False ++ return True ++ ++ def send(self, *caps): ++ sys.stdout.flush() ++ for cap in caps: ++ if cap == 'bold': ++ sys.stdout.buffer.write(b'\x1b[1m') ++ elif isinstance(cap, tuple) and cap[0] == 'setaf': ++ sys.stdout.buffer.write(b'\x1b[%dm' % (cap[1] + 30)) ++ elif cap == 'sgr0': ++ sys.stdout.buffer.write(b'\x1b[m') ++terminfo = _BatchColorTerminfo() + + class Progress: + _enabled = None diff --git a/patches/latexrun-pull-21 b/patches/latexrun-pull-21 new file mode 100644 index 0000000..0b9c5b7 --- /dev/null +++ b/patches/latexrun-pull-21 @@ -0,0 +1,76 @@ +--- latexrun ++++ latexrun +@@ -85,7 +85,7 @@ def main(): + action=ArgParserWarnAction, dest='nowarns', default=set(['underfull']), + help='Enable/disable warning from CLASS, which can be any package name, ' + 'LaTeX warning class (e.g., font), bad box type ' +- '(underfull, overfull, loose, tight), or "all"') ++ '(underfull, overfull, loose, tight), strict parsing (strict-parse), or "all"') + arg_parser.add_argument( + '-O', metavar='DIR', dest='obj_dir', default='latex.out', + help='Directory for intermediate files and control database ' +@@ -236,6 +236,21 @@ def mkdir_p(path): + pass + else: raise + ++def nested_parenthesis_end(string, opening, closing, lax_checking=False): ++ """Return index where closing character corresponds to opening character""" ++ stack = [] ++ for i, c in enumerate(string): ++ if c in opening: ++ stack.append(c) ++ elif c in closing and stack: ++ start_ch = stack.pop() ++ if not lax_checking and opening.index(start_ch) != closing.index(c): ++ # Mismatch, e.g. expected ')', found '}' ++ return -1 ++ if not stack: ++ return i ++ return -1 ++ + class DB: + """A latexrun control database.""" + +@@ -1288,16 +1303,18 @@ class LaTeXFilter: + self.__file_stack.pop() + else: + self.__message('warning', None, +- "extra `)' in log; file names may be wrong ") ++ "extra `)' in log; file names may be wrong") + elif ch == '{': + # TeX uses this for various things we want to ignore, like + # file names and print_mark. Consume up to the '}' +- epos = self.__data.find('}', self.__pos) +- if epos != -1: +- self.__pos = epos + 1 +- else: ++ lax_checking = "strict-parse" in self.__suppress ++ epos = nested_parenthesis_end(self.__data[self.__pos-1:], '{[(', '}])', ++ lax_checking=lax_checking) ++ if epos == -1: + self.__message('warning', None, + "unbalanced `{' in log; file names may be wrong") ++ else: ++ self.__pos += epos + elif ch == '}': + self.__message('warning', None, + "extra `}' in log; file names may be wrong") +@@ -1439,14 +1456,15 @@ class LaTeXFilter: + return + # Back up to the end of the known message text + self.__pos = origpos + m.end() +- if self.__lookingat('\n'): ++ if self.__lookingatre(r'\s*\n'): + # We have a newline, so consume it and look for the + # offending text. + self.__pos += 1 + # If there is offending text, it will start with a font + # name, which will start with a \. +- if 'hbox' in msg and self.__lookingat('\\'): +- self.__consume_line(unwrap=True) ++ if 'hbox' in msg and self.__lookingatre(r'(\[\]\s?)*\s*\\'): ++ consumed = self.__consume_line(unwrap=True) ++ if self.TRACE: print('consuming `<{}>\''.format(consumed)) + msg = self.__simplify_message(msg) + ' (page {})'.format(self.__pageno) + cls = msg.split(None, 1)[0].lower() + self.__message('warning', lineno, msg, cls=cls) diff --git a/patches/latexrun-pull-47 b/patches/latexrun-pull-47 new file mode 100644 index 0000000..18afb05 --- /dev/null +++ b/patches/latexrun-pull-47 @@ -0,0 +1,22 @@ +--- latexrun ++++ latexrun +@@ -861,15 +861,16 @@ class LaTeX(Task): + pages of output. + """ + jobname = outname = None +- for m in re.finditer(r'^Transcript written on "?(.*)\.log"?\.$', stdout, ++ for m in re.finditer(r'^Transcript written on "?(.*?)\.log"?\.$', stdout, + re.MULTILINE | re.DOTALL): + jobname = m.group(1).replace('\n', '') + if jobname is None: + print(stdout, file=sys.stderr) + raise TaskError('failed to extract job name from latex log') +- for m in re.finditer(r'^Output written on "?(.*\.[^ ."]+)"? \([0-9]+ page', ++ for m in re.finditer(r'^Output written on "?(.*?\.[^ ."]+)"? \([0-9]+ (page)?', + stdout, re.MULTILINE | re.DOTALL): +- outname = m.group(1).replace('\n', '') ++ if m.group(2) == "page": ++ outname = m.group(1).replace('\n', '') + if outname is None and not \ + re.search(r'^No pages of output\.$|^! Emergency stop\.$' + r'|^! ==> Fatal error occurred, no output PDF file produced!$', diff --git a/patches/latexrun-pull-61 b/patches/latexrun-pull-61 new file mode 100644 index 0000000..a784377 --- /dev/null +++ b/patches/latexrun-pull-61 @@ -0,0 +1,18 @@ +--- latexrun ++++ latexrun +@@ -457,10 +457,12 @@ class Message(collections.namedtuple( + 'Message', 'typ filename lineno msg')): + def emit(self): + if self.filename: +- if self.filename.startswith('./'): +- finfo = self.filename[2:] +- else: ++ cwd = os.getcwd() ++ if (os.path.isabs(self.filename) and ++ os.path.commonpath([self.filename, cwd]) != cwd): + finfo = self.filename ++ else: ++ finfo = os.path.relpath(self.filename, cwd) + else: + finfo = '' + if self.lineno is not None: diff --git a/patches/latexrun-pull-62 b/patches/latexrun-pull-62 new file mode 100644 index 0000000..6c42f86 --- /dev/null +++ b/patches/latexrun-pull-62 @@ -0,0 +1,31 @@ +--- latexrun ++++ latexrun +@@ -965,7 +965,7 @@ class LaTeX(Task): + + def __clean_messages(self, msgs): + """Make some standard log messages more user-friendly.""" +- have_undefined_reference = False ++ has_errors = any(msg.typ == 'error' for msg in msgs) + for msg in msgs: + if msg.msg == '==> Fatal error occurred, no output PDF file produced!': + msg = msg._replace(typ='info', +@@ -973,10 +973,15 @@ class LaTeX(Task): + if msg.msg.startswith('[LaTeX] '): + # Strip unnecessary package name + msg = msg._replace(msg=msg.msg.split(' ', 1)[1]) +- if re.match(r'Reference .* undefined', msg.msg): +- have_undefined_reference = True +- if have_undefined_reference and \ +- re.match(r'There were undefined references', msg.msg): ++ if has_errors and re.match( ++ r'.*[Rr]erun to get .* right|.* on page .* undefined', msg.msg ++ ): ++ # Warnings on undefined references may occur in high ++ # numbers when documents fail to build. ++ continue ++ if re.match( ++ r"There were (multiply-defined labels|undefined references)", msg.msg ++ ): + # LaTeX prints this at the end so the user knows it's + # worthwhile looking back at the log. Since latexrun + # makes the earlier messages obvious, this is diff --git a/pdfcrop.bzl b/pdfcrop.bzl new file mode 100644 index 0000000..b463f98 --- /dev/null +++ b/pdfcrop.bzl @@ -0,0 +1,52 @@ +def _pdf_crop_impl(ctx): + texlive_path = ctx.var.get("TEXLIVE_FULL_DIR", None) + if texlive_path == None: + fail("Please run setup_texlive.sh to set TEXLIVE_FULL_DIR.") + uncropped = ctx.attr.uncropped.files.to_list()[0] + ctx.actions.run( + mnemonic = "PDFCrop", + executable = "bash", + use_default_shell_env = True, + arguments = [ + ctx.files._pdf_crop_wrapper[0].path, + ctx.files._pdf_crop_script[0].path, + texlive_path, + uncropped.path, + ctx.outputs.output.path, + ], + inputs = depset( + direct = (ctx.files._pdf_crop_script + ctx.files._pdf_crop_wrapper + + [uncropped]), + ), + outputs = [ctx.outputs.output], + ) + +_pdf_crop = rule( + attrs = { + "uncropped": attr.label( + allow_files = True, + ), + "output": attr.output(), + "_pdf_crop_wrapper": attr.label( + allow_files = True, + default = "@bazel_latex//:pdfcrop.sh", + ), + "_pdf_crop_script": attr.label( + allow_files = True, + default = "@pdfcrop//:pdfcrop.pl", + ), + }, + implementation = _pdf_crop_impl, +) + +def pdfcrop(name = "pdfcrop", visibility = [], uncropped = []): + for path in uncropped: + if not path.endswith(".pdf"): + fail + for i, path in enumerate(uncropped): + _pdf_crop( + name = path[:-4] + "-cropper", + uncropped = path, + output = path[:-4] + "-crop.pdf", + visibility = visibility, + ) diff --git a/pdfcrop.sh b/pdfcrop.sh new file mode 100644 index 0000000..ce74ce0 --- /dev/null +++ b/pdfcrop.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +pdfcrop=$1 +texlive=$2 +if [[ $texlive =~ ^[^/].*$ ]]; then + texlive=$PWD/$texlive +fi +echo "Using TeXLive installation: $texlive" +uncropped=$3 +output=$4 + +PATH=$texlive/bin/x86_64:$PATH +PDFCROP=$PWD/$pdfcrop +$PDFCROP $uncropped $output --pdftex diff --git a/repositories.bzl b/repositories.bzl new file mode 100644 index 0000000..d277c76 --- /dev/null +++ b/repositories.bzl @@ -0,0 +1,35 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +def latex_repositories(): + """Adds external dependencies necessary for Bazel-LaTeX. + """ + http_archive( + name = "bazel_latex_latexrun", + build_file_content = "exports_files([\"latexrun\"])", + patches = [ + "@bazel_latex//:patches/latexrun-force-colors", + "@bazel_latex//:patches/latexrun-pull-21", + "@bazel_latex//:patches/latexrun-pull-47", + "@bazel_latex//:patches/latexrun-pull-61", + "@bazel_latex//:patches/latexrun-pull-62", + ], + sha256 = "4e1512fde5a05d1249fd6b4e6610cdab8e14ddba82a7cbb58dc7d5c0ba468c2a", + strip_prefix = "latexrun-38ff6ec2815654513c91f64bdf2a5760c85da26e", + url = "https://github.com/aclements/latexrun/archive/38ff6ec2815654513c91f64bdf2a5760c85da26e.tar.gz", + ) + + http_archive( + name = "pdfcrop", + build_file_content = "exports_files([\"pdfcrop.pl\"])", + sha256 = "4226acd990b3a6db6f9e3bf8949e308b97fa74f8c404023bfb3ce598400a72a4", + strip_prefix = "pdfcrop", + url = "http://mirrors.ctan.org/support/pdfcrop.zip", + ) + + http_archive( + name = "arxiv_latex_cleaner", + build_file_content = """filegroup(name = "all", srcs = glob(["**"]), visibility = ["//visibility:public"])""", + sha256 = "770c65993c964405bb5362ee75039970434ef395872356980e470b6b044ac427", + strip_prefix = "arxiv-latex-cleaner-ea4db65744837bb1205a4fd14b56244b2e639c34", + url = "https://github.com/google-research/arxiv-latex-cleaner/archive/ea4db65744837bb1205a4fd14b56244b2e639c34.tar.gz", + ) diff --git a/run_latex.py b/run_latex.py new file mode 100644 index 0000000..2d7d618 --- /dev/null +++ b/run_latex.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +import glob +import os +import shutil +import subprocess +import sys + +texlive, latexrun, job_name, main_file, output_file = sys.argv[1:6] +sources = sys.argv[6:] + +env = dict(os.environ) +# Generated files (eg. outputs of pdfcrop) are placed under bazel-out/*/bin. +# This references the bin directory so pdflatex can find them. There is +# probably a better way of doing this. +bin_dirs = set() +for source in sources: + if source.startswith("bazel-out"): + bin_dirs.add("%s/%s" % (os.getcwd(), "/".join(source.split("/")[:3]))) +env["TEXINPUTS"] = ".:%s:" % ":".join(sorted(bin_dirs)) + +env["PATH"] = "%s:%s" % (os.path.abspath("%s/bin/x86_64" % texlive), env["PATH"]) +env["PATH"] = "%s:%s" % (os.path.abspath(texlive), env["PATH"]) +env["TEXMFHOME"] = os.path.abspath(texlive) +env["TEXMFVAR"] = os.path.abspath(texlive) +env["SOURCE_DATE_EPOCH"] = "0" + +return_code = subprocess.call( + args=[ + "python3", + latexrun, + "--latex-args=-jobname=" + job_name, + "--latex-cmd=pdflatex", + "--bibtex-cmd=bibtex", + "-Wall", + main_file, + ], + env=env, +) + +if return_code != 0: + sys.exit(return_code) +os.rename(job_name + ".pdf", output_file) diff --git a/setup_texlive.sh b/setup_texlive.sh new file mode 100755 index 0000000..5a553b0 --- /dev/null +++ b/setup_texlive.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +if [ $# -ne 1 ] && [ $# -ne 2 ]; then + echo "This is $(basename $0). Usage:" + echo "$(basename $0) [/path/to/texlive2019-20190410-iso/mounted] [/path/to/install/directory]" + exit 1 +fi + +texlive_mount=$1 +install_dir=$2 + +read -p "Installing TeX Live. This will *OVERWRITE* $install_dir. Continue? [y/N] " -r +if [[ $REPLY =~ ^[Yy]$ ]] +then + rm -rf $install_dir + mkdir -p $install_dir + + infile=$(mktemp) + echo "I" > $infile + + if which tlmgr + then + echo "I" >> $infile + fi + + pushd $texlive_mount + cat $infile | \ + TEXLIVE_INSTALL_PREFIX=$install_dir \ + ./install-tl -no-gui -portable -scheme full + popd + + rm $infile + + # TeXLive will make different bin directories depending on the + # OS; this overrides that to be just x86_64. + tmpdir=$(mktemp -d) + mv $install_dir/bin/*/* $tmpdir + rm -rf $install_dir/bin/* + mkdir $install_dir/bin/x86_64 + mv $tmpdir/* $install_dir/bin/x86_64 + rm -rf $tmpdir + + echo "Success!" + echo "Writing Installation Directory to $HOME/.bazelrc" + echo "build --define TEXLIVE_FULL_DIR=$install_dir" >> ~/.bazelrc + echo "run --define TEXLIVE_FULL_DIR=$install_dir" >> ~/.bazelrc +else + echo "Aborting." +fi diff --git a/view_pdf.sh b/view_pdf.sh new file mode 100755 index 0000000..ee9458c --- /dev/null +++ b/view_pdf.sh @@ -0,0 +1,12 @@ +#!/bin/sh +filename="$(find . -name '*.pdf')" +if type xdg-open > /dev/null 2>&1; then + # X11-based systems (Linux, BSD). + exec xdg-open "${filename}" & +elif type open > /dev/null 2>&1; then + # macOS. + exec open "${filename}" +else + echo "Don't know how to view PDFs on this platform." >&2 + exit 1 +fi -- cgit v1.2.3