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

feat(c): add license support for conan lock files #6329

Merged
merged 14 commits into from
Apr 24, 2024

Conversation

DmitriyLewen
Copy link
Contributor

Description

Add license support for conan dependencies.
Licenses are looked for in conanfile.py files from the conan cache directory.

Related issues

Checklist

  • I've read the guidelines for contributing to this repository.
  • I've followed the conventions in the PR title.
  • I've added tests that prove my fix is effective or that my feature works.
  • I've updated the documentation with the relevant information (if needed).
  • I've added usage information (if the PR introduces new options)
  • I've included a "before" and "after" example to the description (if the PR is a user interface change).

@DmitriyLewen DmitriyLewen self-assigned this Mar 15, 2024
@DmitriyLewen DmitriyLewen changed the title feat(conan): add license support feat(c): add license support for conan lock files Mar 15, 2024
@DmitriyLewen DmitriyLewen marked this pull request as ready for review March 19, 2024 10:42
@DmitriyLewen DmitriyLewen requested a review from knqyf263 as a code owner March 19, 2024 10:42
}
} else if strings.HasPrefix(line, "license") { // cf. https://docs.conan.io/1/reference/conanfile/attributes.html#license
// trim extra characters - e.g. `license = "Apache-2.0"` -> `Apache-2.0`
license = strings.TrimSuffix(strings.TrimPrefix(line, `license = "`), `"`)
Copy link
Collaborator

@knqyf263 knqyf263 Apr 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this example in the doc.

from conan import ConanFile

class MyAndroidNDKRecipe(ConanFile):

  name="my_android_ndk"
  version="1.0"

  def package_info(self):
      self.conf_info.define("tools.android:ndk_path", "bar")

We probably need a regexp like name\s*=\s*"([^"]+)". I want to avoid using regular expressions wherever possible, though.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition, if a conanfile.py is small enough, applying regexp to the file content might be simpler and faster.

    var (
        namePattern = regexp.MustCompile(`^\s*name\s*=\s*"(?P<name>[^"]+)"$`)
        licensePattern = regexp.MustCompile(`^\s*license\s*=\s*"(?P<license>[^"]+)"$`)
    )
    ...

    content, err := io.ReadAll(r)
    if err != nil {...}

    // Find name
    if match := namePattern.FindSubmatch(content); len(match) > 0 {
        name = string(match[namePattern.SubexpIndex("name")])
    }

    // Find license
    if match := licensePattern.FindSubmatch(content); if len(match) > 0 {
        license = string(match[licensePattern.SubexpIndex("version")])
    }

I am not 100% sure which is faster as I have not compared it with processing one line at a time. I was just thinking out loud, and it's not a performance critical process. You can decide.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name="my_android_ndk"

It looks as bug, but we can also parse this case.

We probably need a regexp like name\s*=\s*"([^"]+)". I want to avoid using regular expressions wherever possible, though.

I also don't trust regexp and try not to use them.
I think we can just remove all the spaces.

I am not 100% sure which is faster as I have not compared it with processing one line at a time.

In the files that I checked, name and license fields are placed in first 20-30 lines.
So I think we won't gain much time by doing this.

if a conanfile.py is small enough, applying regexp to the file content might be simpler and faster.

675 lines - https://github.com/aquasecurity/trivy/blob/61af875ec3120ca1fcbae2a7e684173b40ca71b5/pkg/fanal/analyzer/language/c/conan/testdata/cacheDir/.conan/data/openssl/3.0.5/_/_/export/conanfile.py

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added removing extra spaces - 89003d6

Copy link
Collaborator

@knqyf263 knqyf263 Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks as bug, but we can also parse this case.

Why is it a bug? It does not break the Python semantics.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't seen such cases. Also, all test cases use the format name = "foo". Therefore, I assumed that this was a bug or maybe just a typo in the documentation.

In any case, this is easy to solve.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conanfile is written by hand, so any format is possible, isn't it? I think there is a customary preferred format, but it is legitimate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there is a customary preferred format, but it is legitimate.

You are right 👍

pkg/fanal/analyzer/language/c/conan/conan.go Show resolved Hide resolved
}
} else if strings.HasPrefix(line, "license") { // cf. https://docs.conan.io/1/reference/conanfile/attributes.html#license
// remove spaces before and after `=` (if used). e.g. `license = "Apache-2.0"` -> `license="Apache-2.0"`
license = strings.ReplaceAll(line, " ", "")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It introduces another bug with `license = "Apache or MIT".

if license != "" {
break
}
} else if strings.HasPrefix(line, "license") { // cf. https://docs.conan.io/1/reference/conanfile/attributes.html#license
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be more strict. Otherwise, it matches other than the license line. That's why I suggested regexp this time.
https://github.com/conan-io/conan-center-index/blob/55703762e2ca0560fd44d9008e00e61a0a64b23c/recipes/zlib/all/conanfile.py#L90

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like we can't avoid using regexp...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, I don't want to use regexp. I'm still considering a way to parse the license line without regexp, but...

Copy link
Contributor Author

@DmitriyLewen DmitriyLewen Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's better.

Copy link
Contributor Author

@DmitriyLewen DmitriyLewen Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used strings.Cut - 9b5d8ce + 7bedafb

for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())

if strings.HasPrefix(line, "name") { // cf. https://docs.conan.io/1/reference/conanfile/attributes.html#name
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto


func licensesFromCache() (map[string]string, error) {
required := func(filePath string, d fs.DirEntry) bool {
return filepath.Base(filePath) == "conanfile.py"
Copy link
Collaborator

@knqyf263 knqyf263 Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't conanfile.txt used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cache dir contains only conanfile.py files:

root@3fcddc7fe2e5:/app# find /root/.conan -name "conanfile.*"
/root/.conan/data/zlib/1.3.1/_/_/export/conanfile.py

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is conanfile.py required? I'm wondering if we just don't find a project using conanfile.txt as most projects use conanfile.py. But we can handle conanfile.txt if we find such a case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC only conanfile.py contains attributes - https://docs.conan.io/2/reference/conanfile_txt.html#conanfile-txt

Therefore, we can't detect package name/license from conanfile.txt files.
Then we don't need to parse the conanfile.txt files.


func licensesFromCache() (map[string]string, error) {
required := func(filePath string, d fs.DirEntry) bool {
return filepath.Base(filePath) == "conanfile.py"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is conanfile.py required? I'm wondering if we just don't find a project using conanfile.txt as most projects use conanfile.py. But we can handle conanfile.txt if we find such a case.

// e.g. `license = "Apache or MIT"` -> ` "Apache or MIT"` -> `"Apache or MIT"` -> `Apache or MIT`
if name, v, ok := strings.Cut(line, "="); ok && strings.TrimSpace(name) == attributeName {
attr := strings.TrimSpace(v)
return strings.TrimPrefix(strings.TrimSuffix(attr, "\""), "\"")
Copy link
Collaborator

@knqyf263 knqyf263 Apr 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strings.Trim seems better.

Suggested change
return strings.TrimPrefix(strings.TrimSuffix(attr, "\""), "\"")
return strings.Trim(attr, `"`)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 0d67966

@knqyf263 knqyf263 added this pull request to the merge queue Apr 24, 2024
Merged via the queue into aquasecurity:main with commit 5dd9bd4 Apr 24, 2024
17 checks passed
fl0pp5 pushed a commit to altlinux/trivy that referenced this pull request May 6, 2024
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.

Conan license support
2 participants