Skip to content

Hierarchical dependencies #3425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 15 commits into from

Conversation

martinjos
Copy link
Contributor

This branch achieves 4 things:

  1. Global library names
  2. Dependency specs
  3. Hierarchical dependency compilation
  4. Hierarchical dependency tooltips

The general idea of this is to permit the development of mid-level libraries that encapsulate the details of the low-level libraries that they depend on, providing a simpler, more accessible, and perhaps more featureful interface to the same functionality, without bloating the low-level libraries; and to allow common parts of sketches to be factored out. Specifically, it should be possible to do these things without the user having to worry about additional headers to include because of recursive dependencies.

More details follow:

  1. Libraries now have global names. The ordinary name of a library cannot be relied upon to be unique. Nor can documentation URLs (which are not, in any case, intended for identification purposes). This problem is especially exacerbated by forking. A hash, meanwhile, is too specific - you may want to work with different versions of the same library, possibly tweaked by the user immediately prior to compilation.

    The solution is to require each library to have a globally unique name, which should be based on a reverse domain name in the manner of Java or Android package names. For instance, if your project is stored at https://github.com/myaccount/myproject, the global name may be something like io.github.myaccount.myproject (note the use of GitHub's user-only namespace - this is probably best practice even if the user domain is not in use). If myproject contains several subprojects, then they may have global names like "io.github.myaccount.myproject.subproject".

    When the project is forked by otheraccount, the global name of "io.github.myaccount.myproject" should be changed to "io.github.otheraccount.myproject", etc. (unless it is to be considered the same library - e.g. when there is a change of maintainer).

    There is code to deal with legacy projects and projects predating the introduction of the global name, generally by trying to infer the repository/project URL and convert it to a suitable form, and, failing that, falling back on the simple name, combined with the author if present. For this proof-of-concept, the code only looks at the url, name, and author fields of the library.properties file, but other things to try include the repository information in the library.json file if present, repository metadata (.git, .svn...), etc. However, this should probably only ever be a stopgap solution before introducing a proper global name.

  2. Dependencies are found by dependency spec (where present), rather than by header name alone. Next to each #include statement, there can be a comment consisting of //!Lib followed by a dependency spec. A dependency spec consists of a global name optionally followed by : and a version spec. A version spec may be a version number, or a partial version number followed by *, or a version number followed by +, or two version numbers separated by -. These are interpreted in the obvious way. A simple dependency spec is generated for each new #include line when the IDE is used to import a library.

    The global name part alone is used for deciding between alternative libraries, while the version is used to report an incorrect version. (At the moment, this reporting is only partially implemented.)

  3. Dependencies are now found hierarchically (i.e. recursively). All source files within each imported library are examined for #include statements (which may also have dependency specs - see note 2), and additional libraries are added to the project accordingly. These additional imported libraries are also examined for further dependencies, and so on. Naturally, this is implemented such that it is able to deal with dependency loops. The information is also cached - although the cache is updated whenever a source file has changed.

  4. In order to keep users in-the-loop about what libraries they are importing, when a #include statement is hovered over, a tooltip is displayed showing details of all libraries drawn in by that statement, starting with the one directly imported by the #include itself. Friendly name, global name, version and directory are shown for each library.

    The tooltips will always be correct about indirect dependencies (i.e. libraries imported by a library you import). However, they sometimes currently don't have enough context to know that a first-level dependency is affected by previous first-level dependencies (although this shouldn't be that difficult to fix). However, first-level dependency choices are still reported (correctly) with the compiler output, as before.

Further work needed:

  • Find some way of providing a valid preferSet when creating the tooltips, so that they have the context needed to display the correct first-level imports
  • Display a warning when two incompatible libraries are imported (before building, perhaps in tooltip)
  • Extend the version warnings in tooltips to cover recursive dependencies as well.
  • Improve methods for inferring global name.
    • Git/etc repository metadata
    • library.json
  • Improve the caching of dependencies so that it does not recursively check file timestamps multiple times within a single run
  • Discover why it isn't always removing incorrect recursive libs when it rescans
  • Accept only <> for library includes?
  • Display //!Lib spec as a special token in the editor?
  • Improve formatting of tooltip?

@ffissore ffissore self-assigned this Jul 1, 2015
@martinjos
Copy link
Contributor Author

I realise my library import mechanism is kind of terrible. I am not accounting for preprocessor conditionals, for one thing. Also, I have just become aware of the existing thread "build dependency improvement" from this May, the 5 previous pull requests, and issue #236. Apologies for not doing sufficient research before.

However, I was really just going for a proof of concept here. I wanted to explore some of the wider issues around hierarchical dependencies, particularly the problems of accurate library identification and user experience. I don't know whether these have been addressed before.

I am willing to do more work to improve my implementation and/or retrofit some of my ideas into a previous pull request, but I would like to get some feedback from the community first.

@Testato
Copy link

Testato commented Aug 14, 2015

hi martino,

I have a test case, i write two lib, the first may use or may not use the second lib.
But also if the second lib is not used, the user need install it.
The topic is in italian, but the note of version 2.0 explain this in english
http://forum.arduino.cc/index.php/topic,96163.0.html

@ffissore
Copy link
Contributor

ffissore commented Oct 2, 2015

hello @martinjos and sorry for the late reply. your PR was a bit too stuffed and hard to test. smaller PRs are easier to validate and merge. Anyway, we implemented hierarchical dependencies since 9fadb54 which is available with hourly builds http://www.arduino.cc/en/Main/Software#hourly
Can you update your PR so that it features only global names?

@ffissore ffissore closed this Nov 23, 2015
@ffissore ffissore added this to the Release 1.6.7 milestone Nov 23, 2015
@matthijskooijman
Copy link
Collaborator

I believe that the global naming scheme isn't actually useful by itself, but mostly serve to allow the dependency specs to work in a meaningful way (since they now have somewhat unique names to refer to). @ffissore, are you considering adding these dependency specs as well? Or do you have another idea for them?

Perhaps, instead of dependency specs, you could include the library name in the include line? e.g. #include <io.github.myaccount.myproject/myproject.h>?

What you say about forking and changing the unique name, perhaps the library metadata could specify multiple names for a single library? E.g. one primary name (which is changed whenever a library is forked) and zero or more secondary names (which can be used to store the name of the library forked from). When resolving library includes, one could look at primary names first, and only if a primary name isn't available, look at secondary names. Or, perhaps the user could be involved in making choices here, but then it also helps to know that a forked library is a candidate when the original library is being included.

@ffissore
Copy link
Contributor

Uhm seems complicated, but I'll leave this call to @cmaglie

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants