-
-
Notifications
You must be signed in to change notification settings - Fork 2.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Replace Makefile/make.bat with a Python script? #3196
Comments
Here is a (work-in-progress) script I wrote to replace the makefile: #!/usr/bin/env python
# Build script for Sphinx documentation
import os
import shlex
import shutil
import subprocess
import sys
from collections import OrderedDict
# You can set these variables from the command line.
SPHINXOPTS = os.getenv('SPHINXOPTS', '')
SPHINXBUILD = os.getenv('SPHINXBUILD', 'sphinx-build')
PAPER = os.getenv('PAPER', None)
BUILDDIR = os.getenv('BUILDDIR', '_build')
TARGETS = OrderedDict()
def target(function):
TARGETS[function.__name__] = function
return function
# User-friendly check for sphinx-build
def check_sphinx_build():
with open(os.devnull, 'w') as devnull:
try:
if subprocess.call([SPHINXBUILD, '--version'],
stdout=devnull, stderr=devnull) == 0:
return
except FileNotFoundError:
pass
print("The '{0}' command was not found. Make sure you have Sphinx "
"installed, then set the SPHINXBUILD environment variable "
"to point to the full path of the '{0}' executable. "
"Alternatively you can add the directory with the "
"executable to your PATH. If you don't have Sphinx "
"installed, grab it from http://sphinx-doc.org/)"
.format(SPHINXBUILD))
sys.exit(1)
@target
def all():
"""the default target"""
return html()
@target
def clean():
"""remove the build directory"""
shutil.rmtree(BUILDDIR, ignore_errors=True)
def build(builder, success_msg=None, extra_opts=None, outdir=None,
doctrees=True):
builddir = os.path.join(BUILDDIR, outdir or builder)
command = [SPHINXBUILD, '-b', builder]
if doctrees:
command.extend(['-d', os.path.join(BUILDDIR, 'doctrees')])
if extra_opts:
command.extend(extra_opts)
command.extend(shlex.split(SPHINXOPTS))
command.extend(['.', builddir])
print(' '.join(command))
if subprocess.call(command) == 0:
print('Build finished. ' + success_msg.format(builddir))
@target
def html():
"""make standalone HTML files"""
return build('html', 'The HTML pages are in {}.')
@target
def dirhtml():
"""make HTML files named index.html in directories"""
return build('dirhtml', 'The HTML pages are in {}')
@target
def singlehtml():
"""make a single large HTML file"""
return build('singlehtml', 'The HTML page is in {}.')
@target
def pickle():
"""make pickle files"""
return build('pickle', 'Now you can process the pickle files.')
@target
def json():
"""make JSON files"""
return build('json', 'Now you can process the JSON files.')
@target
def htmlhelp():
"""make HTML files and a HTML help project"""
return build('htmlhelp', 'Now you can run HTML Help Workshop with the '
'.hhp project file in {}.')
@target
def qthelp():
"""make HTML files and a qthelp project"""
return build('qthelp', 'Now you can run "qcollectiongenerator" with the '
'.qhcp project file in {0}, like this: \n'
'# qcollectiongenerator {0}/RinohType.qhcp\n'
'To view the help file:\n'
'# assistant -collectionFile {0}/RinohType.qhc')
@target
def devhelp():
"""make HTML files and a Devhelp project"""
return build('devhelp', 'To view the help file:\n'
'# mkdir -p $HOME/.local/share/devhelp/RinohType\n'
'# ln -s {} $HOME/.local/share/devhelp/RinohType\n'
'# devhelp')
@target
def epub():
"""make an epub"""
return build('epub', 'The epub file is in {}.')
@target
def rinoh():
"""make a PDF using rinohtype"""
return build('rinoh', 'The PDF file is in {}.')
@target
def latex():
"""make LaTeX files, you can set PAPER=a4 or PAPER=letter"""
extra_opts = ['-D', 'latex_paper_size={}'.format(PAPER)] if PAPER else None
return build('latex', 'The LaTeX files are in {}.\n'
"Run 'make' in that directory to run these through "
"(pdf)latex (use the 'latexpdf' target to do that "
"automatically).", extra_opts)
@target
def latexpdf():
"""make LaTeX files and run them through pdflatex"""
rc = latex()
print('Running LaTeX files through pdflatex...')
builddir = os.path.join(BUILDDIR, 'latex')
subprocess.call(['make', '-C', builddir, 'all-pdf'])
print('pdflatex finished; the PDF files are in {}.'.format(builddir))
@target
def latexpdfja():
"""make LaTeX files and run them through platex/dvipdfmx"""
rc = latex()
print('Running LaTeX files through platex and dvipdfmx...')
builddir = os.path.join(BUILDDIR, 'latex')
subprocess.call(['make', '-C', builddir, 'all-pdf-ja'])
print('pdflatex finished; the PDF files are in {}.'.format(builddir))
@target
def text():
"""make text files"""
return build('text', 'The text files are in {}.')
@target
def man():
"""make manual pages"""
return build('man', 'The manual pages are in {}.')
@target
def texinfo():
"""make Texinfo files"""
return build('texinfo', 'The Texinfo files are in {}.\n'
"Run 'make' in that directory to run these "
"through makeinfo (use the 'info' target to do "
"that automatically).")
@target
def info():
"""make Texinfo files and run them through makeinfo"""
rc = texinfo()
print('Running Texinfo files through makeinfo...')
builddir = os.path.join(BUILDDIR, 'texinfo')
subprocess.call(['make', '-C', builddir, 'info'])
print('makeinfo finished; the Info files are in {}.'.format(builddir))
@target
def gettext():
"""make PO message catalogs"""
return build('gettext', 'The message catalogs are in {}.', outdir='locale',
doctrees=False)
@target
def changes():
"""make an overview of all changed/added/deprecated items"""
return build('changes', 'The overview file is in {}.')
@target
def xml():
"""make Docutils-native XML files"""
return build('xml', 'The XML files are in {}.')
@target
def pseudoxml():
"""make pseudoxml-XML files for display purposes"""
return build('pseudoxml', 'The pseudo-XML files are in {}.')
@target
def linkcheck():
"""check all external links for integrity"""
return build('linkcheck', 'Look for any errors in the above output or in '
'{}/output.txt.')
@target
def doctest():
"""run all doctests embedded in the documentation (if enabled)"""
return build('doctest', 'Look at the results in {}/output.txt.')
@target
def help():
"""List all targets"""
print("Please use '{} <target>' where <target> is one of"
.format(sys.argv[0]))
width = max(len(name) for name in TARGETS)
for name, target in TARGETS.items():
print(' {name:{width}} {descr}'.format(name=name, width=width,
descr=target.__doc__))
if __name__ == '__main__':
check_sphinx_build()
args = sys.argv[1:] or ['all']
for arg in args:
TARGETS[arg]() Like the makefile, this still includes the project name (RinohType) here and there, which should be moved to a variable. Some targets (latexpdf, info) also call make on a generated makefile. I think these should also be replaced with Python scripts. |
The Makefile is well known and commonly used command. So to provide the Makefile helps the users who don't have python knowledges. BTW, better command line tools are welcome. Since 1.4, we provide make-mode ( |
+1 to keep providing Makefile/make.bat. Just idea:
|
I didn't know about "make mode". This is even better than a Makefile-esque Python script! I agree that there is room for improvement though:
And how can one add a "make target" for another builder? It would be nice if This wouldn't cover all make targets (such as latexpdf), which add another step after building with Sphinx. Would it make sense to add separate builders for this which extend the builder they depend on? |
Another thought: why not specify the source and build directories in |
Sorry, I don't know much about make-mode. But it looks not extensible with APIs.
Personally, I agree that.
If the environment is different, the paths are also different. BTW, +1 for optional argument. It would be useful. |
I'm not sure what you mean with "environment" here. In |
The "top-level" build directory you said is not used in Sphinx application. Anyway, the Sphinx object requires both paths to instantiate. And conf.py are read after Sphinx app invoked. |
@tk0miya Thanks for the explanation. Now I understand the problem. But wouldn't it be possible for sphinx-build to also read |
Yes, it's possible. But I can't still understand the advantage of |
Currently they are specified in the Makefile, but I think the source and build directory paths belong in If they are specified in
Or, similar to @shimizukawa's suggestion above:
|
@tk0miya -- if the |
@bskinn Oh, I'd not noticed that. Could you file it as an issue? |
+1 for the idea of having a Python script to orchestrate the documentation build process, i.e. calling the equivalent of sphinx-build, sphinx-apidoc, etc - I think there are big advantages to this approach! Not everyone has make installed (especially on Windows; it'd be nice if pip-installing sphinx itself was sufficient), and many developers in our community will know Python much better than make (nb: even for non-Python projects the conf.py is in Python so requires some basic Python knowledge). Doc builds should run the same way on all supported platforms, and OS-specific shell scripts work against that goal - making people manually copy sphinx-build command line args and apidoc invocations between a windows make.bat script and unix makefile script is fragile. We wouldn't have to remove the make.py/Makefile stuff for those who genuinely prefer to do it that way, but having the sphinx-quickstart generate a Python script in addition would be really nice and imho provide a cleaner and more cross-platform alternative. It would also give us a good place to give users a helping hand integrating typical usage of sphinx-apidoc for those who need it. Generating a make.bat-style Python orchestration script (call it make.py perhaps?) would be more flexible than just improving the sphinx-build script arguments, since it'd give you a perfect place (and language!) to write custom logic if needed, e.g. if you need to copy or filter files before building the doc, or add some complex apidoc rules for specific packages, or somehow produce different doc sets for different purposes etc (internal vs external-facing doc) - all stuff that's way easier in lovely Python. |
@ben-spiller What is different between the script and |
Hi thanks for the reply. The make.py script I'm suggesting would be an alternative to make.bat/Makefile (not an alternative to sphinx-build - people can alread invoke the raw tool using the script provided with the distribution). The way sphinx is architected (at least as far as I can tell) most projects would need somewhere to put some additional arguments and/or commands that are specific to their build, for example:
Right now, where would be the recommended place to put those? After reading the doc I wasn't sure what the recommended way to orchestrate launching the various processes would be. Of course I could do it manually by writing those args out every time or put together a readme for our developers saying "run apidoc with these args from this directory, then run sphinx-build with these args etc", or copy the required commands into both make.bat and Makefile. But a pure python launcher could be a neater solution to that (I'm thinking just a few lines, very similar to make.bat, perhaps with commented out tetx to show how you'd add sphinx-apidoc). I guess the other way of addressing the underlying requirement would be to add more automation options to the main build so that it's not necessary to run commands like apidoc/apigen separatly before invoking sphinx-build. That would be ideal but potentially a bigger ask. |
I think that is a responsibility of task runner. AFAIK there are many kind of pure python task runners. So, IMO, no reason to implement it again. How about fabric, invoke and so on? |
But if using invoke/fabric to run sphinx-build/apidoc were the best/recommended approach then why does sphinx provide make.bat and Makefile at all? Whatever is provided in-the-box is going to encourage new users down a particular path, for good or ill. This isn't a request for some kind of 'generic' task runner integration with sphinx, more about I still think make.py would be more cross-platform and pythonic than make.bat/Makefile, but actually the more I think about it the best solution to my underlying need is to have more powerful ways to auto-generate rst's without needing a separate invocation of sphinx-apigen/autogen, since it's the complexity of those extra processes which is really driving my desire for a better cross-platform invocation mechanism to invoken them. If all that make.bat/Makefile ever do is invoke sphinx-build, it's easy enough to just ignore them and do that manually and there's not such a compelling reason to need a pure-python implementation of the same. (e.g. I'm thinking of a core capability to generate rst's from .py files a bit like what autosummary_generate is trying to do, but not requiring manual generation of the rst's containing the initial autosummary, or something along the lines of https://autoapi.readthedocs.io/ or https://sphinx-automodapi.readthedocs.io/en/latest/ which I only just found after quite a while trying to get something working with the core sphinx packages). I might create a separate enhancement once I have a more specific/actionable idea, but it's clearly a bit of a separate discussion to this issue. :) Thanks |
Hi! While investigating Sphinx "make mode"
A few questions to @tk0miya and other Sphinx maintainers:
|
Also, it's unclear to me if make mode |
Now #6938 was posted as a successor of the Note: I also don't know the |
Thanks for the quick response @tk0miya! Will have a look at those discussions. |
Closing in favour of #5618. A |
A Python script would make it easier to support the various platforms (#3145, #3194). It would also mean that it can be used in the same way on all platforms, which is handy in a
tox.ini
, for example.The text was updated successfully, but these errors were encountered: