Fortran-FOSS-Programmers / ford

Automatically generates FORtran Documentation from comments within the code.
https://forddocs.readthedocs.io
GNU General Public License v3.0
410 stars 134 forks source link

How to use the `externalize` option? #676

Open mgoonde opened 1 week ago

mgoonde commented 1 week ago

Hello, I have a code with a plugin system, where the base code's documentation is written by FORD, but the plugin documentations are separate ford projects not included in the base. I would like to use the externalize option on the base code, to be able to reference it from the documentation of the plugins (also written by FORD).

I tried setting externalize: true in the base code, and then giving external: local=/path/to/base/doc in the project file of a plugin documentation. But upon running FORD for my plugin, i get the error:

  Correlating information from different parts of your project...
Traceback (most recent call last):
  File "/home/mgunde/.local/bin/ford", line 8, in <module>
    sys.exit(run())
  File "/home/mgunde/.local/lib/python3.10/site-packages/ford/__init__.py", line 489, in run
    main(proj_data, proj_docs)
  File "/home/mgunde/.local/lib/python3.10/site-packages/ford/__init__.py", line 420, in main
    project.correlate()
  File "/home/mgunde/.local/lib/python3.10/site-packages/ford/fortran_project.py", line 340, in correlate
    container.correlate(self)
  File "/home/mgunde/.local/lib/python3.10/site-packages/ford/sourceform.py", line 1273, in correlate
    dtype.correlate(project)
  File "/home/mgunde/.local/lib/python3.10/site-packages/ford/sourceform.py", line 2069, in correlate
    proc.correlate(project)
  File "/home/mgunde/.local/lib/python3.10/site-packages/ford/sourceform.py", line 2485, in correlate
    if self.proto:
AttributeError: 'ExternalBoundProcedure' object has no attribute 'proto'

Without the external: local=... specification, the documentation writes normally (however giving warnings that references are not found, which is normal i guess).

Is this an error due to my wrong use of the external option, or something else?

thanks

haraldkl commented 6 days ago

It sounds to me like you are using it as intended.

It may be that the externalize function does not properly deal with type-bound procedures. I guess, those are not tested in that setting.

haraldkl commented 6 days ago

The test for external projects does contain a type bound procedure:

  type, abstract, public :: solverAborts_type
  contains
    procedure(load_aborts), deferred :: load
  end type solverAborts_type

Can you share some sample code that causes the error?

haraldkl commented 6 days ago

Does #677 help?

mgoonde commented 6 days ago

Can you share some sample code that causes the error?

Sure thing!

This is my fundamental class, called pointers, defined in a module file of the base code:

module gc_pointers_h
  implicit none

  private
  public :: pointers

  type :: pointers
   contains
    procedure :: check
  end type pointers

  interface pointers
    module procedure pointers_constructor
  end interface pointers

  interface
     module function pointers_constructor( gencat )result( this )
      class(*), pointer, intent( in ) :: gencat
      type( pointers ) :: this
    end function pointers_constructor

    module subroutine check( self )
      class( pointers ), intent( in ) :: self
    end subroutine check
  end interface

 CONTAINS

end module gc_pointers_h

Then, the base code specifies a t_method class, which extends pointers:

module gc_method_h

  use gc_pointers_h, only: pointers
  implicit none

  private
  public :: t_method, method_constructor

  type, extends(pointers) :: t_method
   contains
     procedure :: init, run
  end type t_method

  abstract interface
     function method_constructor(ptr, nwords, words) result(this)
       import t_method
       class(t_method), pointer :: this
       class(*), intent(in) :: ptr
       integer, intent(in) :: nwords
       character(*), intent(in) :: words(:)
     end function method_constructor
  end interface

CONTAINS

  function init(self) result(ierr)
    class(t_method), intent(inout) :: self
    integer :: ierr
  end function init

  function run(self) result(ierr)
    class(t_method), intent(inout) :: self
    integer :: ierr
  end function run

end module gc_method_h

And finally, in my plugin code i create an extension of the t_method:

module gc_method_fks_h
  use gc_method_h, only : t_method
  implicit none

  public :: t_method_fks, method_fks_constructor
  private

  !> |cmd| `method fks`
  type, extends( t_method ) :: t_method_fks
   contains
     procedure :: init => method_fks_init
     procedure :: run => method_fks_run
     final :: method_fks_destroy
  end type t_method_fks

  interface method_fks_
     module procedure :: method_fks_constructor
  end interface method_fks_

  interface
     module function method_fks_constructor( ptr, nwords, words )result( this )
      class( t_method ), pointer :: this
      class(*),        intent(in) :: ptr
      integer,         intent( in ) :: nwords
      character(*),    intent( in ) :: words(:)
    end function method_fks_constructor

    module function method_fks_init( self )result(ierr)
      class( t_method_fks ), intent( inout ) :: self
      integer :: ierr
    end function method_fks_init

    module function method_fks_run( self )result(ierr)
      class( t_method_fks ), intent( inout ) :: self
      integer :: ierr
    end function method_fks_run

    module subroutine method_fks_destroy( self )
      type( t_method_fks ), intent(inout) :: self
    end subroutine method_fks_destroy
  end interface

CONTAINS

end module gc_method_fks_h

I managed to track down something, when i comment out the procedure :: check in the definition of type pointers (the first file above), FORD runs without error, and the final documentation properly links the two projects.

mgoonde commented 6 days ago

Does #677 help?

It does not.

haraldkl commented 5 days ago

Thanks! I can reproduce the bug and will have a look.

haraldkl commented 5 days ago

I've now pushed some changes to #677, which makes it work for me. Can you please give it a try?

mgoonde commented 5 days ago

Yeah that seems to work. Awesome, thanks!

There seems to be some small inconsistency however, in that the directory specified in external: local=... seems relative to the location from which i launch FORD, while for comparison the output_dir=.... seems relative to the location of the ford project file.

haraldkl commented 5 days ago

In 6fe3fb2d216c5f21d20e3816ff92d58d74f5a04a I now changed the external definition to be relative to the project directory (if it is relative).

mgoonde commented 4 days ago

That seems to work for me, thanks!

Is it possible to have several paths for the external: local=.... command?

Also, is it desirable to have a hard-stop error when the needed file modules.json is not found? Could it just print a warning and continue without it?

haraldkl commented 4 days ago

Is it possible to have several paths for the external: local=.... command?

Yes. See for example the Ateles source repository.

Could it just print a warning and continue without it?

It could, but that may well go unnoticed. I think, if it was requested by the user in the project, it is justified to abort when that database isn't found.

mgoonde commented 4 days ago

Yes. See for example the Ateles source repository.

Nice, thanks!

It could, but that may well go unnoticed. I think, if it was requested by the user in the project, it is justified to abort when that database isn't found.

Yes, understandable.

How can I pass the command via the command line --config? I tried with quoting the value in the pair, like: --config="external='local=/path'" but that doesn't work.

haraldkl commented 4 days ago

This should work with:

ford -L 'local = /path'
mgoonde commented 4 days ago

Is it possible to add the external modules into the search index?

haraldkl commented 4 days ago

This may depend on the extend of what to add to the search. The search iterates over the generated pages and adds them to the search. Adding all pages from the subprojects into the search appears a little counterproductive to me. I guess it would be possible to iterate over all Fortran objects that have an external_url attribute, to add them to the search additionally, but the question then is what needs to be added as searched text for them.

mgoonde commented 4 days ago

I'm not really sure how this works, but if the search index is generated in a subproject, it could be added to the search of the project which references it as external, since it's already generated in a file anyway. So kind of multiple search index files. If that is possible.