33import sys
44import shutil
55import glob
6+ import zipfile
7+ import tarfile
68
79from typing import Optional , List
810from subprocess import CalledProcessError , check_call
911from pathlib import Path
1012
1113from .Check import Check
12- from ci_tools .functions import install_into_venv
14+ from ci_tools .functions import install_into_venv , unzip_file_to_directory
1315from ci_tools .scenario .generation import create_package_and_install
1416from ci_tools .variables import in_ci , set_envvar_defaults
1517from ci_tools .variables import discover_repo_root
4648ci_doc_dir = os .path .join (REPO_ROOT , "_docs" )
4749sphinx_conf_dir = os .path .join (REPO_ROOT , "doc/sphinx" )
4850generate_mgmt_script = os .path .join (REPO_ROOT , "doc/sphinx/generate_doc.py" )
51+ UNZIPPPED_DIR_NAME = "unzipped"
52+ DOCGEN_DIR_NAME = "docgen"
53+
54+
55+ def unzip_sdist_to_directory (containing_folder : str ) -> str :
56+ """
57+ Unzips the first .zip or .tar.gz file found in the containing_folder to that same folder.
58+
59+ :param containing_folder: The folder to search for .zip or .tar.gz files.
60+ :return: The path to the directory where the archive was extracted.
61+ """
62+ zips = glob .glob (os .path .join (containing_folder , "*.zip" ))
63+
64+ if zips :
65+ return unzip_file_to_directory (zips [0 ], containing_folder )
66+ else :
67+ tars = glob .glob (os .path .join (containing_folder , "*.tar.gz" ))
68+
69+ if tars :
70+ return unzip_file_to_directory (tars [0 ], containing_folder )
71+ else :
72+ raise FileNotFoundError ("No .zip or .tar.gz files found in {}" .format (containing_folder ))
73+
74+
75+ def move_and_rename (source_location : str ) -> str :
76+ """
77+ Moves and renames the extracted sdist folder to a known name.
78+
79+ :param source_location: The path to the extracted sdist folder.
80+ :return: The new path after moving and renaming.
81+ """
82+ new_location = os .path .join (os .path .dirname (source_location ), UNZIPPPED_DIR_NAME )
83+
84+ if os .path .exists (new_location ):
85+ shutil .rmtree (new_location )
86+
87+ os .rename (source_location , new_location )
88+ return new_location
4989
5090
5191# env prep helper functions
5292def create_index_file (readme_location : str , package_rst : str ) -> str :
93+ """
94+ Create the index file content by combining the readme and package rst reference.
95+
96+ :param readme_location: The path to the README file.
97+ :param package_rst: The package rst reference.
98+ :return: The combined index file content.
99+ """
53100 readme_ext = os .path .splitext (readme_location )[1 ]
54101
55102 output = ""
@@ -65,6 +112,13 @@ def create_index_file(readme_location: str, package_rst: str) -> str:
65112
66113
67114def create_index (doc_folder : str , source_location : str , namespace : str ) -> None :
115+ """
116+ Create the index.md file in the documentation folder.
117+
118+ :param doc_folder: The path to the documentation folder.
119+ :param source_location: The path to the source location.
120+ :param namespace: The namespace for the documentation.
121+ """
68122 index_content = ""
69123
70124 package_rst = "{}.rst" .format (namespace )
@@ -174,6 +228,10 @@ def run(self, args: argparse.Namespace) -> int:
174228 logger .error ("This tool requires Python 3.11 or newer. Please upgrade your Python interpreter." )
175229 return 1
176230
231+ if not should_build_docs (package_name ):
232+ logger .info ("Skipping sphinx for {}" .format (package_name ))
233+ continue
234+
177235 self .install_dev_reqs (executable , args , package_dir )
178236
179237 create_package_and_install (
@@ -220,44 +278,36 @@ def run(self, args: argparse.Namespace) -> int:
220278 logger .info (f"Running sphinx against { package_name } " )
221279
222280 # prep env for sphinx
223- doc_folder = os .path .join (staging_directory , "docgen" )
224281 site_folder = os .path .join (package_dir , "website" )
282+ doc_folder = os .path .join (staging_directory , DOCGEN_DIR_NAME )
225283
226- if should_build_docs (package_name ):
227- create_index (doc_folder , package_dir , parsed .namespace )
228-
229- write_version (site_folder , parsed .version )
230- else :
231- logger .info ("Skipping sphinx prep for {}" .format (package_name ))
284+ source_location = move_and_rename (unzip_sdist_to_directory (staging_directory ))
285+ doc_folder = os .path .join (source_location , DOCGEN_DIR_NAME )
286+ create_index (doc_folder , source_location , parsed .namespace )
287+ write_version (site_folder , parsed .version )
232288
233289 # run apidoc
234- if should_build_docs (parsed .name ):
235- if is_mgmt_package (parsed .name ):
236- results .append (self .mgmt_apidoc (doc_folder , package_dir , executable ))
237- else :
238- results .append (self .sphinx_apidoc (staging_directory , package_dir , parsed .namespace , executable ))
290+ output_dir = os .path .join (staging_directory , f"{ UNZIPPPED_DIR_NAME } /{ DOCGEN_DIR_NAME } " )
291+ if is_mgmt_package (package_name ):
292+ results .append (self .mgmt_apidoc (output_dir , package_dir , executable ))
239293 else :
240- logger . info ( "Skipping sphinx source generation for {}" . format ( parsed .name ))
294+ results . append ( self . sphinx_apidoc ( staging_directory , parsed .namespace , executable ))
241295
242296 # build
243- if should_build_docs (package_name ):
244- # Only data-plane libraries run strict sphinx at the moment
245- fail_on_warning = not is_mgmt_package (package_name )
246- results .append (
247- # doc_folder = source
248- # site_folder = output
249- self .sphinx_build (package_dir , doc_folder , site_folder , fail_on_warning , executable )
250- )
251-
252- if in_ci () or args .in_ci :
253- move_output_and_compress (site_folder , package_dir , package_name )
254- if in_analyze_weekly ():
255- from gh_tools .vnext_issue_creator import close_vnext_issue
297+ # Only data-plane libraries run strict sphinx at the moment
298+ fail_on_warning = not is_mgmt_package (package_name )
299+ results .append (
300+ # doc_folder = source
301+ # site_folder = output
302+ self .sphinx_build (package_dir , doc_folder , site_folder , fail_on_warning , executable )
303+ )
256304
257- close_vnext_issue (package_name , "sphinx" )
305+ if in_ci () or args .in_ci :
306+ move_output_and_compress (site_folder , package_dir , package_name )
307+ if in_analyze_weekly ():
308+ from gh_tools .vnext_issue_creator import close_vnext_issue
258309
259- else :
260- logger .info ("Skipping sphinx build for {}" .format (package_name ))
310+ close_vnext_issue (package_name , "sphinx" )
261311
262312 return max (results ) if results else 0
263313
@@ -282,7 +332,9 @@ def sphinx_build(
282332 try :
283333 logger .info ("Sphinx build command: {}" .format (command_array ))
284334
285- self .run_venv_command (executable , command_array , cwd = package_dir , check = True , append_executable = False )
335+ self .run_venv_command (
336+ executable , command_array , cwd = package_dir , check = True , append_executable = False , immediately_dump = True
337+ )
286338 except CalledProcessError as e :
287339 logger .error ("sphinx-build failed for path {} exited with error {}" .format (target_dir , e .returncode ))
288340 if in_analyze_weekly ():
@@ -294,7 +346,6 @@ def sphinx_build(
294346
295347 def mgmt_apidoc (self , output_dir : str , target_folder : str , executable : str ) -> int :
296348 command_array = [
297- executable ,
298349 generate_mgmt_script ,
299350 "-p" ,
300351 target_folder ,
@@ -306,48 +357,53 @@ def mgmt_apidoc(self, output_dir: str, target_folder: str, executable: str) -> i
306357 try :
307358 logger .info ("Command to generate management sphinx sources: {}" .format (command_array ))
308359
309- self .run_venv_command (executable , command_array , cwd = target_folder , check = True , append_executable = False )
360+ self .run_venv_command (
361+ executable , command_array , cwd = target_folder , check = True , append_executable = True , immediately_dump = True
362+ )
310363 except CalledProcessError as e :
311364 logger .error ("script failed for path {} exited with error {}" .format (output_dir , e .returncode ))
312365 return 1
313366 return 0
314367
315- def sphinx_apidoc (self , output_dir : str , target_dir : str , namespace : str , executable : str ) -> int :
316- working_doc_folder = os .path .join (output_dir , " doc" )
368+ def sphinx_apidoc (self , target_dir : str , namespace : str , executable : str ) -> int :
369+ working_doc_folder = os .path .join (target_dir , f" { UNZIPPPED_DIR_NAME } / doc" )
317370 command_array = [
318371 "sphinx-apidoc" ,
319372 "--no-toc" ,
320373 "--module-first" ,
321374 "-o" ,
322- os .path .join (output_dir , "docgen" ), # This is the output folder
323- os .path .join (target_dir , "" ), # This is the input folder
324- os .path .join (target_dir , "test*" ), # This argument and below are "exclude" directory arguments
325- os .path .join (target_dir , "example*" ),
326- os .path .join (target_dir , "sample*" ),
327- os .path .join (target_dir , "setup.py" ),
328- os .path .join (target_dir , "conftest.py" ),
375+ os .path .join (target_dir , f"{ UNZIPPPED_DIR_NAME } /{ DOCGEN_DIR_NAME } " ), # This is the output folder
376+ os .path .join (target_dir , f"{ UNZIPPPED_DIR_NAME } /" ), # This is the input folder
377+ os .path .join (
378+ target_dir , f"{ UNZIPPPED_DIR_NAME } /test*"
379+ ), # This argument and below are "exclude" directory arguments
380+ os .path .join (target_dir , f"{ UNZIPPPED_DIR_NAME } /example*" ),
381+ os .path .join (target_dir , f"{ UNZIPPPED_DIR_NAME } /sample*" ),
382+ os .path .join (target_dir , f"{ UNZIPPPED_DIR_NAME } /setup.py" ),
329383 ]
330384
331385 try :
332386 # if a `doc` folder exists, just leverage the sphinx sources found therein.
333387 if os .path .exists (working_doc_folder ):
334388 logger .info ("Copying files into sphinx source folder." )
335- copy_existing_docs (working_doc_folder , os .path .join (output_dir , "docgen" ))
389+ copy_existing_docs (
390+ working_doc_folder , os .path .join (target_dir , f"{ UNZIPPPED_DIR_NAME } /{ DOCGEN_DIR_NAME } " )
391+ )
336392
337393 # otherwise, we will run sphinx-apidoc to generate the sources
338394 else :
339395 logger .info ("Sphinx api-doc command: {}" .format (command_array ))
340396 self .run_venv_command (executable , command_array , cwd = target_dir , check = True , append_executable = False )
341397 # We need to clean "azure.rst", and other RST before the main namespaces, as they are never
342398 # used and will log as a warning later by sphinx-build, which is blocking strict_sphinx
343- base_path = Path (os .path .join (output_dir , "docgen /" ))
399+ base_path = Path (os .path .join (target_dir , f" { UNZIPPPED_DIR_NAME } / { DOCGEN_DIR_NAME } /" ))
344400 namespace = namespace .rpartition ("." )[0 ]
345401 while namespace :
346402 rst_file_to_delete = base_path / f"{ namespace } .rst"
347403 logger .info (f"Removing { rst_file_to_delete } " )
348404 rst_file_to_delete .unlink (missing_ok = True )
349405 namespace = namespace .rpartition ("." )[0 ]
350406 except CalledProcessError as e :
351- logger .error ("sphinx-apidoc failed for path {} exited with error {}" .format (output_dir , e .returncode ))
407+ logger .error ("sphinx-apidoc failed for path {} exited with error {}" .format (target_dir , e .returncode ))
352408 return 1
353409 return 0
0 commit comments