Skip to content
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

restore python linters #25360

Closed
wants to merge 4 commits into from

Conversation

ericLemanissier
Copy link
Contributor

@ericLemanissier ericLemanissier commented Sep 23, 2024

Summary

  • This reverts commit 5c32f2a
  • removes the last import checker (import-tools)
  • fixes the linter with conan 2

Motivation

As explained earlier, I don't think that removing all python linters is a wise move: it is a very serious reduction on the quality of conan-center recipe.
Random finding of the day:

self.options.with_jpeg == "libjpeg-turbo"
(which is caught by pylint)
I guess that this probably won't add any weigh to this PR, but I have to say that in the context I am, the absence of static code analysis on recipes forces us to question the use of CCI recipes, so basically reconsider our choice of conan as package manager alltogether.

regarding the results of this PR at https://github.com/conan-io/conan-center-index/actions/runs/10991830632?pr=25360

Linter summary (recipes)

  • Instance of 'dict' has no 'get_safe' member: 64 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'shared' member: 52 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'fPIC' member: 27 <= this only happens for recipes not yet ported to conan 2
  • No name 'tools' in module 'conans': 22 <= this only happens for recipes not yet ported to conan 2
  • No name 'ConanFile' in module 'conans': 17 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'target_gl' member: 12 <= this only happens for recipes not yet ported to conan 2
  • No name 'CMake' in module 'conans': 11 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_gl' member: 8 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_webp' member: 7 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'NSSConan' has no 'deps_cpp_info' member: 7 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'ConanOutput' has no 'warn' member: 7 <= this is a true positive
  • Instance of 'dict' has no 'with_pango' member: 6 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_introspection' member: 6 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_libjpeg' member: 5 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_jpeg' member: 5 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'shared_plugins' member: 5 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'gl' member: 5 <= this only happens for recipes not yet ported to conan 2
  • No name 'AutoToolsBuildEnvironment' in module 'conans': 4 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'vk' member: 4 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'quantum_depth' member: 4 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'ign_utils_vendor_cli11' member: 4 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'hdri' member: 4 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'audio' member: 4 <= this only happens for recipes not yet ported to conan 2
  • No name 'VisualStudioBuildEnvironment' in module 'conans': 3 <= this only happens for recipes not yet ported to conan 2
  • No name 'Meson' in module 'conans': 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_zlib' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_xml2' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_vorbis' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_tiff' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_theora' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_sqlite3' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_png' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_ovr' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_opus' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_openjp2' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_openexr' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_ogg' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_lzma' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_libpng' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_lcms' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_jbig' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_imgui' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_heic' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_graphene' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_glm' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_geos' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_freetype' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_eigen' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_dart' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_bzlib' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_bullet' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'windowless_windows_egl_application' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'ui_gallery' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'trade' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'sdl2_application' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'scene_graph' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'glfw_application' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'build_zip_plugin' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'build_rocksdb_cache' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'build_leveldb_cache' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'application' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'NSSConan' has no 'copy' member: 3 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'EmSDKConan' has no 'settings_target' member: 3 <= this is a false positive, which I can fix if there is chance that this PR is erged
  • Unable to import 'conans.tools': 2 <= this only happens for recipes not yet ported to conan 2
  • No name 'MSBuild' in module 'conans': 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'xegl_application' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_yaml' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_ssh2' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_protobuf' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_djvu' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'windowless_wgl_application' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'windowless_ios_application' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'windowless_glx_application' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'windowless_egl_application' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'windowless_cgl_application' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'ui' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'tga_image_converter' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'texture_tools' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'text' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'tbbproxy' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'tbbmalloc' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'target_vk' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'target_headless' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'shaders' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'primitives' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'player' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'mesh_tools' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'magnum_font_converter' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'install_shaders' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'glx_context' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'glx_application' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'egl_context' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'debug_tools' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'cgl_context' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'build_procedural_nodekit' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'al_info' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'Librasterlite2Conan' has no 'deps_cpp_info' member: 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'Librasterlite2Conan' has no 'copy' member: 2 <= this only happens for recipes not yet ported to conan 2
  • layout is missing src_folder argument which should be to src: 1 <= this is a true positive
  • Unexpected keyword argument 'win_bash' in method call: 1 <= to be checked
  • Unexpected keyword argument 'run_environment' in method call: 1 <= to be checked
  • Undefined variable 'units_namespace': 1 <= this is a true positive, why was it ignored in https://github.com/conan-io/conan-center-index/pull/24459/files#diff-170923aa2ee0d9ef9d7b6df1188a6b29b070ce5e731cb984e06a90fe94de9c6eR100 ?
  • No value for argument 'Loader' in function call: 1 <= to be Checked
  • Instance of 'dict' has no 'with_xorg' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_wayland' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_libalsa' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_egl' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_draco' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'with_console' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'wgl_context' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'wav_audio_importer' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'utilities' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'tga_importer' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'scene_converter' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'obj_importer' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'magnum_font' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'image_converter' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'gl_info' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'font_converter' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'enable_wininet_for_http' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'enable_nvtt_cpu_mipmaps' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'enable_geocoder' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'emscripten_application' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'distance_field_converter' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'any_scene_importer' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'any_scene_converter' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'any_image_importer' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'any_image_converter' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'any_audio_importer' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'dict' has no 'android_application' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'RubyConan' has no 'copy' member: 1 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'QtADS' has no 'copy' member: 1 <= this only happens for recipes not yet ported to conan 2

Linter summary (test_package)

  • No name 'CMake' in module 'conans': 18 <= this only happens for recipes not yet ported to conan 2
  • No name 'tools' in module 'conans': 17 <= this only happens for recipes not yet ported to conan 2
  • No name 'ConanFile' in module 'conans': 17 <= this only happens for recipes not yet ported to conan 2
  • Unexpected keyword argument 'run_environment' in method call: 2 <= this only happens for recipes not yet ported to conan 2
  • No name 'RunEnvironment' in module 'conans': 2 <= this only happens for recipes not yet ported to conan 2
  • Instance of 'TestPackageConan' has no 'deps_user_info' member: 2 <= to be checked
  • Instance of 'TestPackageConan' has no 'deps_cpp_info' member: 1 <= to be checked

@ericLemanissier ericLemanissier marked this pull request as draft September 23, 2024 09:43
@ericLemanissier ericLemanissier changed the title Linter v2 [WIP] Linter v2 Sep 23, 2024
@conan-center-bot

This comment has been minimized.

@ericLemanissier ericLemanissier changed the title [WIP] Linter v2 restore python linters Sep 23, 2024
@conan-center-bot

This comment has been minimized.

@ericLemanissier ericLemanissier marked this pull request as ready for review September 23, 2024 11:08
@ericLemanissier
Copy link
Contributor Author

ericLemanissier commented Sep 23, 2024

can be tested with

for r in `yq -r '.required_for_references[]' .c3i/conan_v2_ready_references.yml`
do
    PYTHONPATH=. python3.8 -m pylint --rcfile=linter/pylintrc_recipe `ls recipes/$r/*/conanfile.py` --disable C,R,I
done

@conan-center-bot
Copy link
Collaborator

Conan v1 pipeline ❌

Sorry, the build is only launched for Access Request users. You can request access writing in this issue.

Conan v2 pipeline ❌

Note: Conan v2 builds are now mandatory. Please read our discussion about it.

The v2 pipeline failed. Please, review the errors and note this is required for pull requests to be merged. In case this recipe is still not ported to Conan 2.x, please, ping @conan-io/barbarians on the PR and we will help you.

Sorry, the build is only launched for Access Request users. You can request access writing in this issue.

@jcar87
Copy link
Contributor

jcar87 commented Sep 23, 2024

For full context - the legacy linter, as far as the Conan Center team was concerned, was introduced only with the intention of catching "Conan 2" compatibility of recipes before any level of CI validation of Conan 2 recipes was happening. Any generic pylint checks that happened as a result of it were an added bonus, but never the core of the functionality. It was a mistake from our end to conflate the two things.

in the past several weeks and months - we have noticed the warnings coming from the linter to be:

  • non applicable (false)
  • noisy (to reviewers and contributors).

To the point where it is disrupting both contributors and reviewers.

The dual Conan v1 and Conan v2 CI process should exercise most code paths (but not all) - so at the very least we know that for the mainline supported configurations there should be no issues.

The team is in the process of rewriting the Conan Center CI linter and hooks - one that is more suited the errors we want to catch and fully based on Conan 2.
We expect this to be running on PRs and publishing results in the coming weeks - the linter has already been developed, is currently undergoing internal team testing and we are pending CI integration this coming sprint. It will be also be open sourced once validated.

We did perform an extensive reviews of the types of failures, the frequency of them, and the false vs true positives, and deemed that true positives that are not otherwise caught by the regular pipeline are a significantly infrequent - the last time I check, this was in the range of 4-5 in a whole month. I believe it will take us less than that time to have the new linter running, and thus, we don't think that in this period users will be impacted. Our goal is to ensure their experience is not at all degraded.

We want to ensure the experience of the users is acceptable (free of errors), and that the experience of both contributors are reviewers is not impeded by noise.
If an issue that could have been detected by a linter significantly impacts the user experience, we will make sure our new linter catches this.

@jcar87 jcar87 closed this Sep 23, 2024
@jcar87
Copy link
Contributor

jcar87 commented Sep 23, 2024

Tagging @perseoGI - as he can provide example output of the new linters that we will integrate in our CI.

@ericLemanissier
Copy link
Contributor Author

Do I understand correctly that you are redevelopping a static analyzer for python code, from scratch ?

@jcar87
Copy link
Contributor

jcar87 commented Sep 23, 2024

Do I understand correctly that you are redevelopping a static analyzer for python code, from scratch ?

No

@ericLemanissier
Copy link
Contributor Author

Thanks for taking the time to explain your decision. It's reasuring to know that you don't abandon the idea of static analysis.
In my company, we have the policy to submit all the source-code in our repos to static-analysis, as a complementary seatbelt.
This is why the decision from you to drop pylint directly impacts us, and means that potentially most of the recipes we currently consume from conan-center will have to diverge from CCI at some point, which is obviously not good news.
I understand that this is certainly not your priority, but maybe you could give us some hints of what the future linter will be ? Is it based on something like ruff? prospector ?

@ericLemanissier ericLemanissier deleted the linter-v2 branch September 23, 2024 15:26
@perseoGI
Copy link
Contributor

perseoGI commented Sep 23, 2024

Hi @ericLemanissier, I'm right now in charge of the new linter.
The linter is still under development and thus, it is in a private repository.
We will soon move it into a public repository.
It will be released as a conan custom command so anyone will be able to install it with a simple conan config install <remote>.

I'm attaching a quick recap of our internal PR just for you as you seem very interested in the tech details:

Conan linter to catch up conan specific semantic errors/warnings/notes.

This linter combines different tools:

  • Python analisys:
    • pylint: used as a base for python files
    • custom pylint rules:
      • Ones made with astroid (default pylint way of dealing with custom rules)
      • Ones made with Tree Sitter which returns a tree of nodes, easily queryable.
  • YAML analisys:
    • yamllint: format/lint validation
    • jsonschema: yaml schema validation
    • custom rules to semantically validate
      specific errors using pylint as a base, conan specific errors and

The custom pylint rules have been taken from current conan-center-index repository, with some modifications, and some additions.

We can now detect conan specific/semantic errors which we hope will be useful for CCI contributors.

Usage:

$ conan lint [-h] [-f FORMAT] [-c CONFIG_FILE] [-d [DISABLE ...]] [-e [ENABLE ...]] [more options] [path ...]

This command allows printing in a beautiful manner different rules (it is easily extensive):

$ conan cci:lint tests/assets/cci_lint_repo/recipes/test_recipe/all/conanfile.py

image

It can also accept multiple input files using glob expression or delegating on terminal expansion:

$ conan cci:lint tests/assets/cci_lint_repo/recipes/test_recipe/**/*.yml --format=text

image

It also support json output:

$ conan cci:lint tests/assets/cci_lint_repo/recipes/test_recipe/config.yml --format=json

image

And GitHub format to be used from actions and allow inline markups:

$ conan cci:lint tests/assets/cci_lint_repo/recipes/test_recipe/all/conanfile.py --format=github

image

Real example

The idea of this linter is to be used by contributors before pushing to Conan Center Index.
This could be automated by installing the linter as a git hook

Also, C3I may execute the linter before trying to compile

$ conan cci:lint "recipes/bear/**/*"

image

Customization

By creating a conanlint.yml file, conan cci:lint command will use it as a configuration file.
In it we can define which rules want to be enabled/disabled and the severity of yamllint rules.

By installing the conan configuration, a default conanlint.yml file will be installed. But conan will look for the conanlint.yml file in the current workspace.

A good practice will be to create this file in the Conan Center Index repository.

Pylint default rules

Until now we have discarded the following pylint rules which creates too much noise:

- attribute-defined-outside-init
- broad-exception-caught
- consider-using-f-string
- consider-using-with
- duplicate-code
- fixme
- implicit-str-concat
- import-outside-toplevel
- invalid-name
- line-too-long
- missing-class-docstring
- missing-function-docstring
- missing-module-docstring
- no-else-continue
- no-else-return
- no-member # INFO: fix AST on self.settings recipe property
- raise-missing-from
- too-few-public-methods
- too-many-branches
- too-many-locals
- too-many-statements
- trailing-newlines
- trailing-whitespace
- ungrouped-imports
- unnecessary-lambda-assignment
- unspecified-encoding
- wrong-import-order
- superfluous-parens
- singleton-comparison
- unnecessary-lambda-assignment

Our plan is to disable all pylint rules and only enable what we consider to be really useful for CCI, avoiding as much noise as possible.

If you wish to collaborate, it will be helpful to have a list of rules to enable, (apart from our custom rules)

I hope this shed some light on your concerns.
If you have more questions, we can discus them.
Happy coding 🐸

@ericLemanissier
Copy link
Contributor Author

Again, thanks a lot for all these details. It's really reassuring to see that is actually a continuation of what was already in place, and not a "throw the baby with the bathwater" situation. This plan sound great.
This no-member thing has been annoying with the settings attribute, but I think I found an solution in the form of an inference tip(present in this very PR):

def _looks_like_settings(node: astroid.Attribute) -> bool:
    return node.attrname == "settings"

def infer_settings(node, context):
    return astroid.MANAGER.ast_from_module_name(
        "conans.model.settings").lookup("Settings")[1][0].instantiate_class().infer(context=context)

astroid.MANAGER.register_transform(
    astroid.Attribute,
    inference_tip(infer_settings),
    _looks_like_settings,
)

@perseoGI
Copy link
Contributor

Yes! It is a continuation of the existing work but with more flexibility.
no-member rule is a driving me crazy...
I had to disable it because I tried tons of possibilities.

This is the current state of transform_conanfile.py:

# Class ConanFile doesn't declare all the valid members and functions,
#   some are injected by Conan dynamically to the class.

import textwrap

import astroid
from astroid.builder import AstroidBuilder
from astroid.manager import AstroidManager

def _settings_transform():
    module = AstroidBuilder(AstroidManager()).string_build(
        textwrap.dedent(
            """
            class Settings(object):
                os = None
                arch = None
                build_type = None
                class Compiler:
                    def get_safe(self, attr):
                        pass
                compiler = Compiler()
            """
        )
    )
    return module["Settings"]


def _user_info_build_transform():
    module = AstroidBuilder(AstroidManager()).string_build(
        textwrap.dedent(
            """
            class UserInfoBuild(defaultdict):
                pass
            """
        )
    )
    return module["UserInfoBuild"]


def _requires_transform():
    module = AstroidBuilder(AstroidManager()).string_build(
        textwrap.dedent(
            """
            class RecipeRequires(OrderedDict):
                def __call__(build_require, force_host_context=False):
                    pass
            """
        )
    )
    return module["RecipeRequires"]


def register(_):
    pass


def transform_conanfile(node):
    """Transform definition of ConanFile class so dynamic fields are visible to pylint"""

    str_class = astroid.builtin_lookup("str")
    dict_class = astroid.builtin_lookup("dict")
    info_class = astroid.MANAGER.ast_from_module_name("conans.model.info").lookup(
        "ConanInfo"
    )
    python_requires_class = astroid.MANAGER.ast_from_module_name(
        "conans.client.graph.python_requires"
    ).lookup("PyRequires")

    cpp_info_class = astroid.MANAGER.ast_from_module_name(
        "conans.model.build_info"
    ).lookup("CppInfo")

    dynamic_fields = {
        "conan_data": str_class,
        "user_info_build": [_user_info_build_transform()],
        "info_build": info_class,
        "info": info_class,
        "build_requires": [_requires_transform()],
        "test_requires": [_requires_transform()],
        "tool_requires": [_requires_transform()],
        "cpp_info": cpp_info_class,
        "python_requires": [str_class, python_requires_class],
        "recipe_folder": str_class,
        "settings_build": [_settings_transform()],
        "settings_target": [_settings_transform()],
        "settings_validate": [_settings_transform()],
        "conf": dict_class,
    }

    for f, t in dynamic_fields.items():
        node.locals[f] = [i for i in t]


# TODO dont work, filtered on handle_message
# def transform_settings(node):
#     if isinstance(node, nodes.ClassDef) and node.name == "ConanFile":
#         for method in node.mymethods():
#             if method.name == "build":
#                 settings_class = _settings_transform()
#
#                 # Iterate over all statements within the function
#                 for subnode in method.nodes_of_class(nodes.Expr):
#                     for attr_node in subnode.nodes_of_class(nodes.Attribute):
#                         if (
#                             isinstance(attr_node.expr, nodes.Attribute) and
#                             isinstance(attr_node.expr.expr, nodes.Name) and
#                             attr_node.expr.expr.name == "self" and
#                             attr_node.expr.attrname == "settings"
#                         ):
#                             # Replace self.settings with an instance of our custom Settings class
#                             attr_node.expr = settings_class.instantiate_class()
#                             break  # Assuming a single usage per function

astroid.MANAGER.register_transform(
    astroid.ClassDef,
    transform_conanfile,
    lambda node: node.qname() == "conans.model.conan_file.ConanFile",
)

# astroid.MANAGER.register_transform(nodes.FunctionDef, transform_settings)

I've to try your solution, but I wanted to share the code with you to see if you can help a bit!
My idea was to type all dynamic attributes but some of them were not being successfully infer, causing no-member rule to be raised sometimes.

@ericLemanissier
Copy link
Contributor Author

I think the cause of pylint considering self.settings as a list is this line https://github.com/conan-io/conan/blob/28739c763000afc908ebc64eba717b7d01784de1/conans/model/conan_file.py#L98 so if you don't feel my code above is a solution, another way could be to rewrite self.settings = [self.settings] as self.settings: Settings = [self.settings] or self.settings: Union[list, Settings] = [self.settings]
Or maybe try the type hint on this line https://github.com/conan-io/conan/blob/28739c763000afc908ebc64eba717b7d01784de1/conans/model/conan_file.py#L45 settings: Settings = None or settings: Union[list, Settings, None] = None
I did not try any modifications of conan_file.py.

Appart from that, I don't think we actually need the _requires_transform "mock". Using the actual astroid.MANAGER.ast_from_module_name("conans.model.requires").lookup("BuildRequirements"), astroid.MANAGER.ast_from_module_name("conans.model.requires").lookup("ToolRequirements") and astroid.MANAGER.ast_from_module_name("conans.model.requires").lookup("TestRequirements") seems to work (the same you already have for PyRequires)

Also, I think pylint correctly deduces the type of self.cpp_info by analysing this line, and knowing the actual type to self.cpp from this line and this line, so probably don't have to force it in node.locals['cpp_info']

To answer your question above, I'll have a hard time listing you an actual list of "must have" checkers, considering the very long list at https://pylint.readthedocs.io/en/latest/user_guide/checkers/features.html
The usual workflow we use for static analysis is: enable all checks, then analyse all the repository/ies, then disable all the checks with the lowest value/noise ratio. This allows to make profit of new checkers automatically when new versions of the static analyser provide new checks. But I guess this is what you already did, so not much information here.

Once again, thanks @perseoGI for having this constructive technical discussion. It's nice to see that collaboration is possible, despite the initial reaction to this PR. By the way, you may want to continue the discussion in a more suitable space than a closed PR on the wrong repo ?

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