55
66package software .amazon .smithy .docgen .core .integrations ;
77
8+ import static java .lang .String .format ;
9+ import static software .amazon .smithy .docgen .core .DocgenUtils .normalizeNewlines ;
10+ import static software .amazon .smithy .docgen .core .DocgenUtils .runCommand ;
11+
812import java .util .List ;
913import java .util .Set ;
1014import java .util .logging .Logger ;
15+ import software .amazon .smithy .codegen .core .CodegenException ;
1116import software .amazon .smithy .docgen .core .DocFormat ;
1217import software .amazon .smithy .docgen .core .DocGenerationContext ;
1318import software .amazon .smithy .docgen .core .DocIntegration ;
1419import software .amazon .smithy .docgen .core .DocSettings ;
1520import software .amazon .smithy .docgen .core .sections .sphinx .ConfSection ;
1621import software .amazon .smithy .docgen .core .sections .sphinx .MakefileSection ;
22+ import software .amazon .smithy .docgen .core .sections .sphinx .RequirementsSection ;
1723import software .amazon .smithy .docgen .core .sections .sphinx .WindowsMakeSection ;
1824import software .amazon .smithy .docgen .core .writers .SphinxMarkdownWriter ;
1925import software .amazon .smithy .model .shapes .ServiceShape ;
2026import software .amazon .smithy .utils .SmithyInternalApi ;
2127
2228/**
2329 * Adds Sphinx project scaffolding for compatible formats.
30+ *
31+ * <p>This integration runs in low priority to allow other integrations to generate
32+ * files that will be picked up by sphinx-build. To have an integration reliably run
33+ * after this, override {@link DocIntegration#runAfter} with the output of
34+ * {@link SphinxIntegration#name} in the list. Similarly, to guarantee an integration
35+ * is run before this, override {@link DocIntegration#runBefore} with the same argument.
36+ *
37+ * <p>To customize the project files generated by this integration, you can make use
38+ * of {@link DocIntegration#interceptors} to intercept and modify the files before
39+ * they're written. The following named code sections are used:
40+ *
41+ * <ul>
42+ * <li>{@link ConfSection}: Creates the {@code conf.py}
43+ * <li>{@link MakefileSection}: Creates the {@code Makefile} build script for unix.
44+ * <li>{@link WindowsMakeSection}: Creates the {@code make.bat} build script for
45+ * Windows.
46+ * <li>{@link RequirementsSection}: Creates the {@code requirements.txt} used to
47+ * build the docs. Any dependencies here will be installed into the environment
48+ * used to run {@code sphinx-build}.
49+ * </ul>
2450 */
2551@ SmithyInternalApi
2652public final class SphinxIntegration implements DocIntegration {
2753 private static final String MARKDOWN_FORMAT = "sphinx-markdown" ;
2854 private static final Set <String > FORMATS = Set .of (MARKDOWN_FORMAT );
2955 private static final Logger LOGGER = Logger .getLogger (SphinxIntegration .class .getName ());
3056
57+ // The default requirements needed to build the docs.
58+ private static final List <String > REQUIREMENTS = List .of (
59+ "Sphinx==7.2.6" ,
60+ "myst-parser==2.0.0" ,
61+ "linkify-it-py==2.0.2"
62+ );
63+
64+ @ Override
65+ public byte priority () {
66+ // Run at the end so that any integration-generated changes can happen.
67+ return -128 ;
68+ }
69+
3170 @ Override
3271 public List <DocFormat > docFormats (DocSettings settings ) {
3372 return List .of (
@@ -38,16 +77,26 @@ public List<DocFormat> docFormats(DocSettings settings) {
3877 @ Override
3978 public void customize (DocGenerationContext context ) {
4079 if (!FORMATS .contains (context .docFormat ().name ())) {
41- LOGGER .finest (String . format (
80+ LOGGER .finest (format (
4281 "Format %s is not a Sphinx-compatible format, skipping Sphinx project setup." ,
4382 context .docFormat ().name ()
4483 ));
4584 return ;
4685 }
4786 // TODO: add some way to disable project file generation
48- LOGGER .finest ("Generating Sphinx project files." );
87+ LOGGER .info ("Generating Sphinx project files." );
88+ writeRequirements (context );
4989 writeConf (context );
5090 writeMakefile (context );
91+ runSphinx (context );
92+ }
93+
94+ private void writeRequirements (DocGenerationContext context ) {
95+ context .writerDelegator ().useFileWriter ("requirements.txt" , writer -> {
96+ writer .pushState (new RequirementsSection (context , REQUIREMENTS ));
97+ REQUIREMENTS .forEach (writer ::write );
98+ writer .popState ();
99+ });
51100 }
52101
53102 private void writeConf (DocGenerationContext context ) {
@@ -155,4 +204,85 @@ private void writeMakefile(DocGenerationContext context) {
155204 writer .popState ();
156205 });
157206 }
207+
208+ private void runSphinx (DocGenerationContext context ) {
209+ var baseDir = context .fileManifest ().getBaseDir ();
210+
211+ LOGGER .info ("Flushing writers in preparation for sphinx-build." );
212+ context .writerDelegator ().flushWriters ();
213+
214+ // Python must be available to run sphinx
215+ try {
216+ LOGGER .info ("Attempting to discover python3 in order to run sphinx." );
217+ runCommand ("python3 --version" , baseDir );
218+ } catch (CodegenException e ) {
219+ LOGGER .warning ("Unable to find python3 on path. Skipping automatic HTML doc build." );
220+ logManualBuildInstructions (context );
221+ return ;
222+ }
223+
224+ // TODO: detect if the user's existing python environment can be used
225+ // You can get a big JSON document describing the python environment from
226+ // `pip inspect` that has all the information we need.
227+ try {
228+ // First, we create a virtualenv to install dependencies into. This is necessary
229+ // to not pollute the user's environment.
230+ runCommand ("python3 -m venv venv" , baseDir );
231+
232+ // Next, install the dependencies into the venv.
233+ runCommand ("./venv/bin/pip install -r requirements.txt" , baseDir );
234+
235+ // Finally, run sphinx itself.
236+ runCommand ("./venv/bin/sphinx-build -M dirhtml content build" , baseDir );
237+
238+ System .out .printf (normalizeNewlines ("""
239+ Successfully built HTML docs. They can be found in "%1$s".
240+
241+ Other output formats can also be built. A python virtual environment \
242+ has been created at "%2$s" containing the build tools needed for \
243+ manually building the docs in other formats. See the virtual \
244+ environment docs for information on how to activate it: \
245+ https://docs.python.org/3/library/venv.html#how-venvs-work
246+
247+ Once the environment is activated, run `make dirhtml` from "%3$s" to \
248+ to build the docs, substituting dirhtml for whatever format you wish \
249+ to build.
250+
251+ To build the docs without activating the virtual environment, simply \
252+ run `./venv/bin/sphinx-build -M dirhtml content build` from "%3$s", \
253+ similarly substituting dirhtml for your desired format.
254+
255+ See sphinx docs for other output formats you can choose: \
256+ https://www.sphinx-doc.org/en/master/usage/builders/index.html
257+
258+ """ ),
259+ baseDir .resolve ("build/dirhtml" ),
260+ baseDir .resolve ("venv" ),
261+ baseDir
262+ );
263+ } catch (CodegenException e ) {
264+ LOGGER .warning ("Unable to automatically build HTML docs: " + e );
265+ logManualBuildInstructions (context );
266+ }
267+ }
268+
269+ private void logManualBuildInstructions (DocGenerationContext context ) {
270+ // TODO: try to get this printed out in the projection section
271+ System .out .printf (normalizeNewlines ("""
272+ To build the HTML docs manually, you need to first install the python \
273+ dependencies. These can be found in the `requirements.txt` file in \
274+ "%1$s". The easiest way to install these is by running `pip install \
275+ -r requirements.txt`. Depending on your environment, you may need to \
276+ instead install them from your system package manager, or another \
277+ source.
278+
279+ Once the dependencies are installed, run `make dirhtml` from \
280+ "%1$s". Other output formats can also be built. See sphinx docs for \
281+ other output formats: \
282+ https://www.sphinx-doc.org/en/master/usage/builders/index.html
283+
284+ """ ),
285+ context .fileManifest ().getBaseDir ()
286+ );
287+ }
158288}
0 commit comments