From 9a88d79bb20a4cfce069e4fc7d64fbc226a15f2d Mon Sep 17 00:00:00 2001 From: Nathan Kimmel Date: Tue, 9 Jul 2024 22:14:04 -0400 Subject: [PATCH 01/14] Fix of pydoctor crash when creating symlink to index.html When using pydoctor to create documentation for a single root module the symlink to index.html is created before index.html is created. This causes a crash when running pydoctor in Windows. The creation of the symlink has been moved to a separate function that is called after index.html is created. This resolves the issue. --- pydoctor/driver.py | 1 + pydoctor/templatewriter/writer.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pydoctor/driver.py b/pydoctor/driver.py index 1bf8b9cd1..fb973a7f8 100644 --- a/pydoctor/driver.py +++ b/pydoctor/driver.py @@ -129,6 +129,7 @@ def make(system: model.System) -> None: if not options.htmlsummarypages: subjects = system.rootobjects writer.writeIndividualFiles(subjects) + writer.writeIndexPage(system) if options.makeintersphinx: if not options.makehtml: diff --git a/pydoctor/templatewriter/writer.py b/pydoctor/templatewriter/writer.py index 06df1d5b4..a5ca2d00b 100644 --- a/pydoctor/templatewriter/writer.py +++ b/pydoctor/templatewriter/writer.py @@ -97,7 +97,8 @@ def writeSummaryPages(self, system: model.System) -> None: T = time.time() search.write_lunr_index(self.build_directory, system=system) system.msg('html', "took %fs"%(time.time() - T), wantsnl=False) - + + def writeIndexPage(self, system: model.System) -> None: if len(system.root_names) == 1: # If there is just a single root module it is written to index.html to produce nicer URLs. # To not break old links we also create a symlink from the full module name to the index.html @@ -107,7 +108,7 @@ def writeSummaryPages(self, system: model.System) -> None: root_module_path.unlink() # not using missing_ok=True because that was only added in Python 3.8 and we still support Python 3.6 except FileNotFoundError: - pass + system.msg('file not found:', root_module_path) root_module_path.symlink_to('index.html') def _writeDocsFor(self, ob: model.Documentable) -> None: From 937b90b23f9b87cc0c393e31cb8ba81f1ecce4a1 Mon Sep 17 00:00:00 2001 From: Nathan Kimmel Date: Wed, 10 Jul 2024 11:43:17 -0400 Subject: [PATCH 02/14] Renamed writeIndexPage to writeIndexSymlink for improved clarity. --- pydoctor/driver.py | 2 +- pydoctor/templatewriter/writer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pydoctor/driver.py b/pydoctor/driver.py index fb973a7f8..9aee1b0a3 100644 --- a/pydoctor/driver.py +++ b/pydoctor/driver.py @@ -129,7 +129,7 @@ def make(system: model.System) -> None: if not options.htmlsummarypages: subjects = system.rootobjects writer.writeIndividualFiles(subjects) - writer.writeIndexPage(system) + writer.writeIndexSymlink(system) if options.makeintersphinx: if not options.makehtml: diff --git a/pydoctor/templatewriter/writer.py b/pydoctor/templatewriter/writer.py index a5ca2d00b..0ff7cdd04 100644 --- a/pydoctor/templatewriter/writer.py +++ b/pydoctor/templatewriter/writer.py @@ -98,7 +98,7 @@ def writeSummaryPages(self, system: model.System) -> None: search.write_lunr_index(self.build_directory, system=system) system.msg('html', "took %fs"%(time.time() - T), wantsnl=False) - def writeIndexPage(self, system: model.System) -> None: + def writeIndexSymlink(self, system: model.System) -> None: if len(system.root_names) == 1: # If there is just a single root module it is written to index.html to produce nicer URLs. # To not break old links we also create a symlink from the full module name to the index.html From 0193d7ba3ccfcdc0f085c9d2e538040ae551158b Mon Sep 17 00:00:00 2001 From: Nathan Kimmel Date: Wed, 10 Jul 2024 12:05:27 -0400 Subject: [PATCH 03/14] Added writeIndexSymlink to InMemoryWriter. --- pydoctor/test/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pydoctor/test/__init__.py b/pydoctor/test/__init__.py index 09a9e65b9..f76382ffa 100644 --- a/pydoctor/test/__init__.py +++ b/pydoctor/test/__init__.py @@ -70,6 +70,11 @@ def writeSummaryPages(self, system: model.System) -> None: Rig the system to not created the inter sphinx inventory. """ system.options.makeintersphinx = False + + def writeIndexSymlink(self, system: model.System) -> None: + """ + Does nothing. + """ def _writeDocsFor(self, ob: model.Documentable) -> None: """ From 13d479d3c65e99437a3b263ecefa6228306ca71a Mon Sep 17 00:00:00 2001 From: Nathan Kimmel Date: Wed, 10 Jul 2024 12:34:05 -0400 Subject: [PATCH 04/14] Added call to writeIndexSymlink to mirror usage in make. --- pydoctor/test/test_templatewriter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydoctor/test/test_templatewriter.py b/pydoctor/test/test_templatewriter.py index dbc143967..ee6fbe2cf 100644 --- a/pydoctor/test/test_templatewriter.py +++ b/pydoctor/test/test_templatewriter.py @@ -139,6 +139,7 @@ def test_basic_package(tmp_path: Path) -> None: root, = system.rootobjects w._writeDocsFor(root) w.writeSummaryPages(system) + w.writeIndexSymlink(system) for ob in system.allobjects.values(): url = ob.url if '#' in url: From 2a6abe7fa740d7a561aaa64ca2cafd8621207937 Mon Sep 17 00:00:00 2001 From: rocketengineer1982 <36516928+rocketengineer1982@users.noreply.github.com> Date: Wed, 10 Jul 2024 12:40:21 -0400 Subject: [PATCH 05/14] Removing unnecessary system message when root_module.html is not found. Co-authored-by: tristanlatr <19967168+tristanlatr@users.noreply.github.com> --- pydoctor/templatewriter/writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydoctor/templatewriter/writer.py b/pydoctor/templatewriter/writer.py index 0ff7cdd04..6770fb888 100644 --- a/pydoctor/templatewriter/writer.py +++ b/pydoctor/templatewriter/writer.py @@ -108,7 +108,7 @@ def writeIndexSymlink(self, system: model.System) -> None: root_module_path.unlink() # not using missing_ok=True because that was only added in Python 3.8 and we still support Python 3.6 except FileNotFoundError: - system.msg('file not found:', root_module_path) + pass root_module_path.symlink_to('index.html') def _writeDocsFor(self, ob: model.Documentable) -> None: From 4a1eeb7d119a0d84d9c1c14874df9c6f0d16245a Mon Sep 17 00:00:00 2001 From: Nathan Kimmel Date: Wed, 10 Jul 2024 13:19:54 -0400 Subject: [PATCH 06/14] Added an if statement to avoid calling writeIndexSymlink when option --html-subject is used. --- pydoctor/driver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pydoctor/driver.py b/pydoctor/driver.py index 9aee1b0a3..ef6763eee 100644 --- a/pydoctor/driver.py +++ b/pydoctor/driver.py @@ -129,7 +129,8 @@ def make(system: model.System) -> None: if not options.htmlsummarypages: subjects = system.rootobjects writer.writeIndividualFiles(subjects) - writer.writeIndexSymlink(system) + if not options.htmlsubjects: + writer.writeIndexSymlink(system) if options.makeintersphinx: if not options.makehtml: From de4f0ba89de7d0a7ae7fd4471abf3d36401648c8 Mon Sep 17 00:00:00 2001 From: Nathan Kimmel Date: Wed, 10 Jul 2024 13:21:10 -0400 Subject: [PATCH 07/14] Added writeIndexSymlink to IWriter. --- pydoctor/templatewriter/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pydoctor/templatewriter/__init__.py b/pydoctor/templatewriter/__init__.py index c0ba892c1..263b13a56 100644 --- a/pydoctor/templatewriter/__init__.py +++ b/pydoctor/templatewriter/__init__.py @@ -75,6 +75,11 @@ def writeIndividualFiles(self, obs: Iterable[Documentable]) -> None: """ Called last. """ + + def writeIndexSymlink(self, system: System) -> None: + """ + Called after writeIndividualFiles when option --html-subject is not used. + """ class Template(abc.ABC): """ From 848bc05a5a91f1fb37aa38468f1bb9481ed32478 Mon Sep 17 00:00:00 2001 From: Nathan Kimmel Date: Fri, 9 Aug 2024 15:53:47 -0400 Subject: [PATCH 08/14] Changed index.html from symlink to hardlink --- pydoctor/driver.py | 2 +- pydoctor/templatewriter/__init__.py | 4 ++-- pydoctor/templatewriter/writer.py | 7 ++++--- pydoctor/test/__init__.py | 2 +- pydoctor/test/test_templatewriter.py | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/pydoctor/driver.py b/pydoctor/driver.py index ef6763eee..2ed95a315 100644 --- a/pydoctor/driver.py +++ b/pydoctor/driver.py @@ -130,7 +130,7 @@ def make(system: model.System) -> None: subjects = system.rootobjects writer.writeIndividualFiles(subjects) if not options.htmlsubjects: - writer.writeIndexSymlink(system) + writer.writeIndexHardlink(system) if options.makeintersphinx: if not options.makehtml: diff --git a/pydoctor/templatewriter/__init__.py b/pydoctor/templatewriter/__init__.py index 263b13a56..fd3633fbf 100644 --- a/pydoctor/templatewriter/__init__.py +++ b/pydoctor/templatewriter/__init__.py @@ -73,10 +73,10 @@ def writeSummaryPages(self, system: System) -> None: def writeIndividualFiles(self, obs: Iterable[Documentable]) -> None: """ - Called last. + Called third. """ - def writeIndexSymlink(self, system: System) -> None: + def writeIndexHardlink(self, system: System) -> None: """ Called after writeIndividualFiles when option --html-subject is not used. """ diff --git a/pydoctor/templatewriter/writer.py b/pydoctor/templatewriter/writer.py index 6770fb888..e7377739f 100644 --- a/pydoctor/templatewriter/writer.py +++ b/pydoctor/templatewriter/writer.py @@ -3,6 +3,7 @@ import itertools from pathlib import Path +import shutil from typing import IO, Iterable, Type, TYPE_CHECKING from pydoctor import model @@ -98,10 +99,10 @@ def writeSummaryPages(self, system: model.System) -> None: search.write_lunr_index(self.build_directory, system=system) system.msg('html', "took %fs"%(time.time() - T), wantsnl=False) - def writeIndexSymlink(self, system: model.System) -> None: + def writeIndexHardlink(self, system: model.System) -> None: if len(system.root_names) == 1: # If there is just a single root module it is written to index.html to produce nicer URLs. - # To not break old links we also create a symlink from the full module name to the index.html + # To not break old links we also create a hardlink from the full module name to the index.html # file. This is also good for consistency: every module is accessible by .html root_module_path = (self.build_directory / (list(system.root_names)[0] + '.html')) try: @@ -109,7 +110,7 @@ def writeIndexSymlink(self, system: model.System) -> None: # not using missing_ok=True because that was only added in Python 3.8 and we still support Python 3.6 except FileNotFoundError: pass - root_module_path.symlink_to('index.html') + shutil.copy(root_module_path, 'index.html') def _writeDocsFor(self, ob: model.Documentable) -> None: if not ob.isVisible: diff --git a/pydoctor/test/__init__.py b/pydoctor/test/__init__.py index f76382ffa..c89bef467 100644 --- a/pydoctor/test/__init__.py +++ b/pydoctor/test/__init__.py @@ -71,7 +71,7 @@ def writeSummaryPages(self, system: model.System) -> None: """ system.options.makeintersphinx = False - def writeIndexSymlink(self, system: model.System) -> None: + def writeIndexHardlink(self, system: model.System) -> None: """ Does nothing. """ diff --git a/pydoctor/test/test_templatewriter.py b/pydoctor/test/test_templatewriter.py index ee6fbe2cf..c6e0188e2 100644 --- a/pydoctor/test/test_templatewriter.py +++ b/pydoctor/test/test_templatewriter.py @@ -139,7 +139,7 @@ def test_basic_package(tmp_path: Path) -> None: root, = system.rootobjects w._writeDocsFor(root) w.writeSummaryPages(system) - w.writeIndexSymlink(system) + w.writeIndexHardlink(system) for ob in system.allobjects.values(): url = ob.url if '#' in url: From 73d21209b31a4359d3801075a99114fb8f37fe19 Mon Sep 17 00:00:00 2001 From: Nathan Kimmel Date: Fri, 9 Aug 2024 17:04:56 -0400 Subject: [PATCH 09/14] Fixed bug involving incomplete index.html path in writeIndexHardlink --- pydoctor/templatewriter/writer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pydoctor/templatewriter/writer.py b/pydoctor/templatewriter/writer.py index e7377739f..9988aeda8 100644 --- a/pydoctor/templatewriter/writer.py +++ b/pydoctor/templatewriter/writer.py @@ -110,7 +110,8 @@ def writeIndexHardlink(self, system: model.System) -> None: # not using missing_ok=True because that was only added in Python 3.8 and we still support Python 3.6 except FileNotFoundError: pass - shutil.copy(root_module_path, 'index.html') + hardlink_path = (self.build_directory / 'index.html') + shutil.copy(hardlink_path, root_module_path) def _writeDocsFor(self, ob: model.Documentable) -> None: if not ob.isVisible: From 9bb5b82c080932a04bab5c8571b7c48d6064c37c Mon Sep 17 00:00:00 2001 From: rocketengineer1982 <36516928+rocketengineer1982@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:59:01 -0400 Subject: [PATCH 10/14] Update pydoctor/templatewriter/__init__.py Co-authored-by: tristanlatr <19967168+tristanlatr@users.noreply.github.com> --- pydoctor/templatewriter/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydoctor/templatewriter/__init__.py b/pydoctor/templatewriter/__init__.py index fd3633fbf..28896ba58 100644 --- a/pydoctor/templatewriter/__init__.py +++ b/pydoctor/templatewriter/__init__.py @@ -76,7 +76,7 @@ def writeIndividualFiles(self, obs: Iterable[Documentable]) -> None: Called third. """ - def writeIndexHardlink(self, system: System) -> None: + def writeLinks(self, system: System) -> None: """ Called after writeIndividualFiles when option --html-subject is not used. """ From b4dcf1960b8e2ef2c84dc8ed1122638f5d3dbdb5 Mon Sep 17 00:00:00 2001 From: rocketengineer1982 <36516928+rocketengineer1982@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:59:06 -0400 Subject: [PATCH 11/14] Update pydoctor/templatewriter/writer.py Co-authored-by: tristanlatr <19967168+tristanlatr@users.noreply.github.com> --- pydoctor/templatewriter/writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydoctor/templatewriter/writer.py b/pydoctor/templatewriter/writer.py index 9988aeda8..6725ebfff 100644 --- a/pydoctor/templatewriter/writer.py +++ b/pydoctor/templatewriter/writer.py @@ -99,7 +99,7 @@ def writeSummaryPages(self, system: model.System) -> None: search.write_lunr_index(self.build_directory, system=system) system.msg('html', "took %fs"%(time.time() - T), wantsnl=False) - def writeIndexHardlink(self, system: model.System) -> None: + def writeLinks(self, system: model.System) -> None: if len(system.root_names) == 1: # If there is just a single root module it is written to index.html to produce nicer URLs. # To not break old links we also create a hardlink from the full module name to the index.html From d98e3194fc6cdd298fa7fee0b7e751a80c14e929 Mon Sep 17 00:00:00 2001 From: rocketengineer1982 <36516928+rocketengineer1982@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:59:12 -0400 Subject: [PATCH 12/14] Update pydoctor/test/__init__.py Co-authored-by: tristanlatr <19967168+tristanlatr@users.noreply.github.com> --- pydoctor/test/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydoctor/test/__init__.py b/pydoctor/test/__init__.py index c89bef467..45e8cf406 100644 --- a/pydoctor/test/__init__.py +++ b/pydoctor/test/__init__.py @@ -71,7 +71,7 @@ def writeSummaryPages(self, system: model.System) -> None: """ system.options.makeintersphinx = False - def writeIndexHardlink(self, system: model.System) -> None: + def writeLinks(self, system: model.System) -> None: """ Does nothing. """ From 51bf91d452ab7619b49708502d30f0f5154d133f Mon Sep 17 00:00:00 2001 From: tristanlatr Date: Thu, 12 Sep 2024 21:17:07 -0400 Subject: [PATCH 13/14] Add option --use-hardlinks and add changelog enrty. --- README.rst | 2 ++ pydoctor/driver.py | 2 +- pydoctor/options.py | 4 ++++ pydoctor/templatewriter/writer.py | 18 +++++++++++------- pydoctor/test/test_commandline.py | 27 +++++++++++++++++++++++++++ pydoctor/test/test_templatewriter.py | 2 +- 6 files changed, 46 insertions(+), 9 deletions(-) diff --git a/README.rst b/README.rst index 27745e6f1..5a920d0d3 100644 --- a/README.rst +++ b/README.rst @@ -77,6 +77,8 @@ in development ^^^^^^^^^^^^^^ * Trigger a warning when several docstrings are detected for the same object. +* Fix WinError caused by the failure of the symlink creation process. + Pydoctor should now run on windows without the need to be administrator. pydoctor 24.3.3 ^^^^^^^^^^^^^^^ diff --git a/pydoctor/driver.py b/pydoctor/driver.py index 2ed95a315..89ac7f418 100644 --- a/pydoctor/driver.py +++ b/pydoctor/driver.py @@ -130,7 +130,7 @@ def make(system: model.System) -> None: subjects = system.rootobjects writer.writeIndividualFiles(subjects) if not options.htmlsubjects: - writer.writeIndexHardlink(system) + writer.writeLinks(system) if options.makeintersphinx: if not options.makehtml: diff --git a/pydoctor/options.py b/pydoctor/options.py index 60cebc1ae..1c26a9bb0 100644 --- a/pydoctor/options.py +++ b/pydoctor/options.py @@ -244,6 +244,9 @@ def get_parser() -> ArgumentParser: parser.add_argument( '--mod-member-order', dest='mod_member_order', default="alphabetical", choices=["alphabetical", "source"], help=("Presentation order of module/package members. (default: alphabetical)")) + parser.add_argument( + '--use-hardlinks', default=False, action='store_true', dest='use_hardlinks', + help=("Always copy files instead of creating a symlink (hardlinks will be automatically used if the symlink process failed).")) parser.add_argument('-V', '--version', action='version', version=f'%(prog)s {__version__}') @@ -375,6 +378,7 @@ class Options: nosidebar: int = attr.ib() cls_member_order: 'Literal["alphabetical", "source"]' = attr.ib() mod_member_order: 'Literal["alphabetical", "source"]' = attr.ib() + use_hardlinks: bool = attr.ib() def __attrs_post_init__(self) -> None: # do some validations... diff --git a/pydoctor/templatewriter/writer.py b/pydoctor/templatewriter/writer.py index 6725ebfff..52aabfe67 100644 --- a/pydoctor/templatewriter/writer.py +++ b/pydoctor/templatewriter/writer.py @@ -102,16 +102,20 @@ def writeSummaryPages(self, system: model.System) -> None: def writeLinks(self, system: model.System) -> None: if len(system.root_names) == 1: # If there is just a single root module it is written to index.html to produce nicer URLs. - # To not break old links we also create a hardlink from the full module name to the index.html + # To not break old links we also create a link from the full module name to the index.html # file. This is also good for consistency: every module is accessible by .html root_module_path = (self.build_directory / (list(system.root_names)[0] + '.html')) + root_module_path.unlink(missing_ok=True) # introduced in Python 3.8 + try: - root_module_path.unlink() - # not using missing_ok=True because that was only added in Python 3.8 and we still support Python 3.6 - except FileNotFoundError: - pass - hardlink_path = (self.build_directory / 'index.html') - shutil.copy(hardlink_path, root_module_path) + if system.options.use_hardlinks: + # The use wants only harlinks, so simulate an OSError + # to jump directly to the hardlink part. + raise OSError() + root_module_path.symlink_to('index.html') + except OSError: + hardlink_path = (self.build_directory / 'index.html') + shutil.copy(hardlink_path, root_module_path) def _writeDocsFor(self, ob: model.Documentable) -> None: if not ob.isVisible: diff --git a/pydoctor/test/test_commandline.py b/pydoctor/test/test_commandline.py index 7634b2138..5178af7ea 100644 --- a/pydoctor/test/test_commandline.py +++ b/pydoctor/test/test_commandline.py @@ -272,3 +272,30 @@ def test_make_intersphix(tmp_path: Path) -> None: assert [p.name for p in tmp_path.iterdir()] == ['objects.inv'] assert inventory.is_file() assert b'Project: acme-lib\n# Version: 20.12.0-dev123\n' in inventory.read_bytes() + +def test_index_symlink(tmp_path: Path) -> None: + """ + Test that the default behaviour is to create symlinks, at least on unix. + + For windows users, this has not been a success, so we automatically fallback to copying the file now. + See https://github.com/twisted/pydoctor/issues/808, https://github.com/twisted/pydoctor/issues/720. + """ + import platform + exit_code = driver.main(args=['--html-output', str(tmp_path), 'pydoctor/test/testpackages/basic/']) + assert exit_code == 0 + link = (tmp_path / 'basic.html') + assert link.exists() + if platform.system() == 'Windows': + assert link.is_symlink() or link.is_file() + else: + assert link.is_symlink() + +def test_index_hardlink(tmp_path: Path) -> None: + """ + Test for option --use-hardlink wich enforce the usage of harlinks. + """ + exit_code = driver.main(args=['--use-hardlink', '--html-output', str(tmp_path), 'pydoctor/test/testpackages/basic/']) + assert exit_code == 0 + assert (tmp_path / 'basic.html').exists() + assert not (tmp_path / 'basic.html').is_symlink() + assert (tmp_path / 'basic.html').is_file() diff --git a/pydoctor/test/test_templatewriter.py b/pydoctor/test/test_templatewriter.py index c6e0188e2..7b64f06c1 100644 --- a/pydoctor/test/test_templatewriter.py +++ b/pydoctor/test/test_templatewriter.py @@ -139,7 +139,7 @@ def test_basic_package(tmp_path: Path) -> None: root, = system.rootobjects w._writeDocsFor(root) w.writeSummaryPages(system) - w.writeIndexHardlink(system) + w.writeLinks(system) for ob in system.allobjects.values(): url = ob.url if '#' in url: From 54a5f50a482c31cd3c8d2d71c1d5377e53f22a08 Mon Sep 17 00:00:00 2001 From: tristanlatr Date: Thu, 12 Sep 2024 21:19:43 -0400 Subject: [PATCH 14/14] Fix tests --- .github/workflows/unit.yaml | 2 +- pydoctor/templatewriter/writer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit.yaml b/.github/workflows/unit.yaml index 647a52d8e..161a6ba3b 100644 --- a/.github/workflows/unit.yaml +++ b/.github/workflows/unit.yaml @@ -20,7 +20,7 @@ jobs: strategy: matrix: # Re-enable 3.13-dev when https://github.com/zopefoundation/zope.interface/issues/292 is fixed - python-version: [pypy-3.7, 3.7, 3.8, 3.9, '3.10', 3.11, '3.12'] + python-version: [pypy-3.8, 3.8, 3.9, '3.10', 3.11, '3.12'] os: [ubuntu-22.04] include: - os: windows-latest diff --git a/pydoctor/templatewriter/writer.py b/pydoctor/templatewriter/writer.py index 52aabfe67..80802b3cb 100644 --- a/pydoctor/templatewriter/writer.py +++ b/pydoctor/templatewriter/writer.py @@ -113,7 +113,7 @@ def writeLinks(self, system: model.System) -> None: # to jump directly to the hardlink part. raise OSError() root_module_path.symlink_to('index.html') - except OSError: + except (OSError, NotImplementedError): # symlink is not implemented for windows on pypy :/ hardlink_path = (self.build_directory / 'index.html') shutil.copy(hardlink_path, root_module_path)