anod.spec
=========

.. py:module:: anod.spec


Attributes
----------

.. autoapisummary::

   anod.spec.__version__
   anod.spec.SUPPORTED_API
   anod.spec.logger
   anod.spec.spec_logger
   anod.spec.BUILD_PRIMITIVE


Classes
-------

.. autoapisummary::

   anod.spec.Anod


Functions
---------

.. autoapisummary::

   anod.spec.check_api_version
   anod.spec.parse_command
   anod.spec.has_primitive
   anod.spec.fetch_attr


Module Contents
---------------

.. py:data:: __version__
   :value: '1.4'


.. py:data:: SUPPORTED_API

.. py:data:: logger

.. py:data:: spec_logger

.. py:data:: BUILD_PRIMITIVE

.. py:function:: check_api_version(version: str) -> None

   Make sure there are no API mismatch.

   :raise: AnodError if the API is not supported


.. py:function:: parse_command(command: collections.abc.Sequence[str], build_space: e3.anod.buildspace.BuildSpace) -> list[str]

   Parse a command line formatting each string.

   :param command: the command line (a list of string)
   :param build_space: a build space object


.. py:function:: has_primitive(anod_instance: Anod, name: Literal['download'] | PRIMITIVE) -> bool

   Return True if the primitive `name` is supported.

   Note that download is currently considered as a primitive in that context.

   :param anod_instance: an Anod instance
   :param name: name of the primitive ('build', 'install'...)


.. py:function:: fetch_attr(instance: Any, name: str, default_value: Any) -> Any

   Return an attribute or the default value if missing.

   Unlike `getattr(instance, name, default_value)`, this works only on
   attributes present in the class (so class attributes or properties) and
   this does not hide AttributeError exceptions that getting an existing
   attribute might raise.


.. py:class:: Anod(qualifier: str | dict[str, str | bool | Iterable[str]] | None, kind: PRIMITIVE, jobs: int = 1, env: e3.env.BaseEnv | None = None, *, parse_qualifiers: bool = True)

   Anod base class.

   To write an Anod specification file, you'll need to subclass Anod. A very
   basic Anod specification file could be:

   .. code-block:: python

       from e3.anod.spec import Anod

       class MyProduct(Anod):
           pass

   All attributes starting with ``spec_`` are reserved by the driver and must
   not be overwritten.

   Several attributes are set when loading the spec:

   :cvar api_version: which API version is in use
   :cvar data_files: set of yaml files associated with the spec
   :cvar spec_checksum: the sha1 of the specification file content
   :cvar spec_dir: directory where the specification files are located
   :cvar name: the basename of the specification file (without the .anod
        extension)
   :cvar sandbox: e3.anod.sandbox.SandBox object shared by all Anod instances
   :vartype sandbox: e3.anod.sandbox.SandBox | None

   Some attributes are meant to be overwritten in the specification file:

   :cvar source_pkg_build: a dictionary associating Anod.SourceBuilder to the
       Anod.Source names

   Some attributes are here to simply the writing of specification files.
   They are part of the Anod API:

   :cvar Dependency: the e3.anod.deps.Dependency class
   :cvar Package: the e3.anod.package.Package class
   :cvar Source: the e3.anod.package.Source class
   :cvar SourceBuilder: the e3.anod.package.SourceBuilder class
   :cvar ThirdPartySourceBuilder: the e3.anod.package.ThirdPartySourceBuilder

   :ivar uid: unique identifier for the instance, None until the instance
       has been activated with AnodDriver.activate()
   :vartype uid: str | None


   .. py:attribute:: spec_checksum
      :value: ''



   .. py:attribute:: spec_dir
      :value: ''



   .. py:attribute:: sandbox
      :type:  e3.anod.sandbox.SandBox | None
      :value: None



   .. py:attribute:: name
      :value: ''



   .. py:attribute:: api_version
      :value: '1.4'



   .. py:attribute:: data_files
      :type:  tuple[str, Ellipsis]
      :value: ()



   .. py:attribute:: Dependency


   .. py:attribute:: Package


   .. py:attribute:: BuildVar


   .. py:attribute:: Source


   .. py:attribute:: SharedSource


   .. py:attribute:: SourceBuilder


   .. py:attribute:: ExternalSourceBuilder


   .. py:attribute:: ThirdPartySourceBuilder


   .. py:attribute:: deps
      :type:  dict[str, Anod]


   .. py:attribute:: kind


   .. py:attribute:: jobs
      :value: 1



   .. py:attribute:: __build_space
      :type:  e3.anod.buildspace.BuildSpace | None
      :value: None



   .. py:attribute:: log


   .. py:attribute:: env


   .. py:attribute:: uid
      :value: ''



   .. py:attribute:: _config
      :type:  dict | None
      :value: None



   .. py:attribute:: _pre
      :type:  dict[str, Any] | None
      :value: None



   .. py:property:: qualifier
      :type: str



   .. py:property:: enable_name_generator
      :type: bool


      State if the name generation must be enabled.

      If true, then both the 'component' and the 'build_space_name' are generated.



   .. py:property:: readme_info
      :type: tuple[str, str] | None


      Return readme location and final filename.

      .. note:: This property make sens only if a component is declared.

      :return: A tuple with a relative path to spec directory where the find the
          content and the final basename for the readme.



   .. py:method:: declare_qualifiers_and_components(qualifiers_manager: e3.anod.qualifiers_manager.QualifiersManager) -> None

      Configure all the qualifiers and components.

      This method must be overridden in the user spec to actually configure
      the QualifiersManager.

      All the qualifiers must be declared using the declare_tag_qualifier and
      declare_key_value_qualifier spec_parameter_manager method.

      All the components must be declared using the declare_component
      method of QualifiersManager class.

      :param qualifiers_manager: the QualifiersManager instance to be configured.



   .. py:property:: args
      :type: dict[str, str | bool | frozenset[str]]


      Access to final qualifier values (with defaults set).



   .. py:property:: base_name
      :type: str


      Set the base name used for the name generation.

      This method is used to provide the base name to be used by the name generator.
      By default, this base name is 'self.name' (the spec name without .anod).

      It can be overloaded in the user spec.



   .. py:property:: build_space_name
      :type: str


      Return an automatic build_space_name.

      If the component name is not None (from the component method), it will return
      the component name for consistency reasons.

      :return: self.name if the name generator is disabled and the generated
          build_space name otherwise.
      :rtype: str | None



   .. py:property:: build_space
      :type: e3.anod.buildspace.BuildSpace



   .. py:property:: has_package
      :type: bool


      Return true if the spec defines a binary package.



   .. py:method:: bind_to_sandbox(sandbox: e3.anod.sandbox.SandBox) -> None

      Bind spec instance to a physical Anod sandbox.

      Binding an Anod instance to a sandbox will set the
      build_space attribute.



   .. py:method:: check_shared_libraries_closure(prefix: str | None = None, ignored_libs: list[str] | None = None, ldd_output: str | None = None, case_sensitive: bool | None = None) -> dict[str, list[tuple[str, str]]]

      Sanity check the shared library closure.

      Make sure that the libraries only depend on authorized system shared
      libraries.

      Raise an error when a problem is detected.

      It is possible to use the *ldd_output* parameter to mimic the ``ldd``
      call. In that case, the *ldd_output* should look like::

          /a/b/c.so:
                  d.so => /e/f/d.so
                  g.so => /a/b/g.so
          /a/b/h.so:
                  g.so => /a/b/g.so

      .. note::
          Indented lines above use the TAB character.

      .. note::
          This method may be used only on hosts with an ``ldd`` tool in the
          ``PATH``. If the tool may not be found with :func:`~e3.os.fs.which`,
          and *ldd_output* is not provided, a :exc:`FileNotFoundError`
          exception is raised.

      :param prefix: The path where to find the library to be checked.
      :param ignored_libs: The list of additional system libraries authorized
          in the closure.
      :param ldd_output: An ``ldd`` command output.
      :param case_sensitive: State if the comparison of a library name and the
          value in *ignored_libs* should be case-sensitive or not. If
          ``None``, the comparison is case-sensitive, but on Windows hosts.

      :return: A dictionary, where keys are the analyzed files, and the
          values are the shared libraries linked to that file (a tuple made
          of the file name and its path).

      :raise AnodError: if some of the shared libraries in *prefix* (or in
          *ldd_output*) is not in the same directory as the analysed element.
      :raise FileNotFoundError: if *ldd_output* is not provided and ``ldd``
          application cannot be found in the ``PATH``.



   .. py:method:: load_config_file(extended: bool = False, suffix: str | None = None, selectors: dict | None = None) -> Any

      Load a YAML config file associated with the current module.

      This function looks for a YAML starting with the spec basename. The
      list of available file is set by the data_files parameter when
      initializing the spec.

      :param suffix: suffix of the configuration file (default is None)
      :param extended: if True then a special yaml parser is used with
          ability to use case statement
      :param selectors: additional selectors for extended mode



   .. py:method:: __getitem__(key: str) -> Any

      Access build_space attributes and pre callback values directly.

      Allow accessing all build_space attributes directly by using
      __getitem__, e.g. self['PKG_DIR'] to access
      self.build_space.pkg_dir values.

      Also, directly access items returned by the ``pre`` callback.



   .. py:method:: get_qualifier(qualifier_name: str) -> str | bool | frozenset[str] | None

      Return a qualifier value.

      Requires that qualifiers_manager attribute has been initialized and its parse
      method called.

      :return: The qualifier value:
          - A string for key value qualifiers
          - A bool for tag qualifiers
          - ``None`` if the name_generator is disabled



   .. py:method:: primitive(pre: str | None = None, post: str | None = None, version: collections.abc.Callable[Ellipsis, str] | None = None, require: collections.abc.Callable[[Anod], bool] | None = None, post_install: bool = False) -> collections.abc.Callable
      :classmethod:


      Declare an anod primitive.

      Catch all exceptions and raise AnodError with the traceback

      :param pre: None or name of method in the spec to call before running
          the primitive. The method takes a unique parameter `self` and
          returns a dict
      :param post: None or "install". This parameter is obsolete and should
          not be used. Prefer "post_install=True" to achieve the same effect
      :param version: None or a callback function returning the version
          that will be evaluated as a string. This callback is called
          after running the primitive
      :param require: None or a special function to call before running the
          primitive. The function takes a unique parameter `self` and
          returns a boolean
      :param post_install: if True call the install primitive after the build
          primitive. Note that between the two a binary package might be generated
          depending on the context. This is equivalent to post="install"
      :raise: AnodError



   .. py:property:: package
      :type: e3.anod.package.Package | None


      Return binary package creation recipe.

      If None don't create a binary package, needs a component name set.



   .. py:property:: component
      :type: str | None


      Return component name.

      If None, don't created a component (nor a binary package).
      :return: None if the name generator is disabled and the generated name
      otherwise (possibly None if no component is required)



   .. py:property:: module_name
      :type: str


      For backward compatibility purpose.



   .. py:property:: anod_id
      :type: str


      For backward compatibility purpose.



   .. py:property:: source_pkg_build
      :type: list[e3.anod.package.SourceBuilder] | None


      Return list of SourceBuilder defined in the specification file.



   .. py:method:: shell(*command: str, parse_shebang: bool = True, output: e3.os.process.DEVNULL_VALUE | e3.os.process.PIPE_VALUE | str | IO | None = None, python_executable: None = None, **kwargs: Any) -> e3.os.process.Run

      Run a subprocess using e3.os.process.Run.

      Contrary to what is done in e3.os.process.Run parse_shebang
      defaults to True and output is by default set to the anod
      build space log stream.

      Note that calling shell() raises an exception when the
      process returns an exit code that is not 0.

      Same options as e3.os.process.Run with some small differences:

      :param python_executable: kept for backward compatibility but ignored
      :param output: by default set to anod build space log stream
      :param parse_shebang: by default set True

      :raise: ShellError



