Skip to content

Commit c076ae7

Browse files
committed
python/pylint: sorta working version...
1 parent 36db748 commit c076ae7

File tree

12 files changed

+478
-15
lines changed

12 files changed

+478
-15
lines changed

.codacy/codacy.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
runtimes:
22
3+
34
tools:
45
6+

cmd/analyze.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,24 @@ var analyzeCmd = &cobra.Command{
198198
switch toolToAnalyze {
199199
case "eslint":
200200
// nothing
201+
case "pylint":
202+
pylint := config.Config.Tools()["pylint"]
203+
if pylint == nil {
204+
log.Fatal("Pylint is not installed. Please install it using 'codacy-cli install'.")
205+
}
206+
pylintInstallationDirectory := pylint.Info()["installDir"]
207+
pythonRuntime := config.Config.Runtimes()["python"]
208+
pythonBinary := pythonRuntime.Info()["python"]
209+
210+
log.Printf("Running %s...\n", toolToAnalyze)
211+
if outputFile != "" {
212+
log.Println("Output will be available at", outputFile)
213+
}
214+
tools.RunPylint(workDirectory, pylintInstallationDirectory, pythonBinary, args, autoFix, outputFile)
215+
201216
case "":
202217
log.Fatal("You need to specify a tool to run analysis with, e.g., '--tool eslint'", toolToAnalyze)
218+
203219
default:
204220
log.Fatal("Trying to run unsupported tool: ", toolToAnalyze)
205221
}

cmd/init.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,18 +74,24 @@ func configFileTemplate(tools []tools.Tool) string {
7474

7575
// Default version
7676
eslintVersion := "9.3.0"
77+
pylintVersion := "2.13.9"
7778

7879
for _, tool := range tools {
7980
if tool.Uuid == "f8b29663-2cb2-498d-b923-a10c6a8c05cd" {
8081
eslintVersion = tool.Version
8182
}
83+
if tool.Uuid == "34225275-f79e-4b85-8126-c7512c987c0d" {
84+
pylintVersion = tool.Version
85+
}
8286
}
8387

8488
return fmt.Sprintf(`runtimes:
8589
90+
8691
tools:
8792
- eslint@%s
88-
`, eslintVersion)
93+
- pylint@%s
94+
`, eslintVersion, pylintVersion)
8995
}
9096

9197
func buildRepositoryConfigurationFiles(token string) error {

cmd/install.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ func fetchRuntimes(config *cfg.ConfigType) {
3535
if err != nil {
3636
log.Fatal(err)
3737
}
38+
case "python":
39+
err := cfg.InstallPython(r)
40+
if err != nil {
41+
log.Fatal(err)
42+
}
3843
default:
3944
log.Fatal("Unknown runtime:", r.Name())
4045
}
@@ -52,6 +57,12 @@ func fetchTools(config *cfg.ConfigType) {
5257
fmt.Println(err.Error())
5358
log.Fatal(err)
5459
}
60+
case "pylint":
61+
pythonRuntime := config.Runtimes()["python"]
62+
err := cfg.InstallPylint(pythonRuntime, tool)
63+
if err != nil {
64+
log.Fatal(err)
65+
}
5566
default:
5667
log.Fatal("Unknown tool:", tool.Name())
5768
}

codacy-cli

16.4 MB
Binary file not shown.

codacy-cli-local

16.4 MB
Binary file not shown.

config/pylint-utils.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os/exec"
7+
"path"
8+
)
9+
10+
func getInfoPylint() map[string]string {
11+
pythonRuntime := Config.Runtimes()["python"]
12+
13+
pythonFolder := fmt.Sprintf("%s@%s", pythonRuntime.Name(), pythonRuntime.Version())
14+
pythonInstallDir := path.Join(Config.RuntimesDirectory(), pythonFolder, "python")
15+
pylintPath := path.Join(pythonInstallDir, "bin", "pylint")
16+
17+
return map[string]string{
18+
"installDir": pythonInstallDir,
19+
"pylint": pylintPath,
20+
}
21+
22+
}
23+
24+
// installing in the python runtime because
25+
// f you install Pylint in a different tools folder, it will not work properly because of the following reasons:
26+
// Python Virtual Environment Isolation:
27+
// When you install Pylint in the tools folder (separately from Python), you are essentially mixing environments.
28+
// The python binary located in /Users/yasmin/.cache/codacy/runtimes/[email protected]/python/bin/python3 will not have
29+
// access to packages installed elsewhere unless properly referenced.
30+
// PYTHONPATH Limitation:
31+
// You tried passing the tools directory via PYTHONPATH.
32+
// While PYTHONPATH allows modules to be found, it doesn't register packages installed by pip properly.
33+
// Pylint Binary (pylint):
34+
// The pylint binary expects the pylint package to be installed within the same Python environment that is running it.
35+
// If you run:
36+
// /Users/.cache/codacy/runtimes/[email protected]/python/bin/python3 -m pylint
37+
// It will look for the pylint module installed under its site-packages directory within:
38+
// /Users/.cache/codacy/runtimes/[email protected]/python/lib/python3.10/site-packages
39+
func InstallPylint(pythonRuntime *Runtime, pylint *Runtime) error {
40+
log.Println("Installing Pylint")
41+
42+
pythonInfo := getInfoPython(pythonRuntime)
43+
44+
pythonBinary := pythonInfo["python"]
45+
46+
// to install pylint using oython binary
47+
cmd := exec.Command(pythonBinary, "-m", "pip", "install",
48+
fmt.Sprintf("pylint==%s", pylint.Version()),
49+
)
50+
51+
output, err := cmd.CombinedOutput()
52+
if err != nil {
53+
return fmt.Errorf("error installing Pylint: %v\nOutput: %s", err, string(output))
54+
}
55+
56+
log.Println("Pylint installed successfully")
57+
return nil
58+
}

config/python-utils.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package config
2+
3+
import (
4+
"codacy/cli-v2/utils"
5+
"fmt"
6+
"log"
7+
"os"
8+
"path"
9+
"runtime"
10+
)
11+
12+
func getInfoPython(r *Runtime) map[string]string {
13+
pythonFolder := fmt.Sprintf("%s@%s", r.Name(), r.Version())
14+
installDir := path.Join(Config.RuntimesDirectory(), pythonFolder)
15+
16+
var pythonBinary, pipBinary string
17+
18+
//todo check windows dire,
19+
//had to add python subdir to path since tar extracts it there
20+
if runtime.GOOS == "windows" {
21+
pythonBinary = path.Join(installDir, "Scripts", "python.exe")
22+
pipBinary = path.Join(installDir, "Scripts", "pip.exe")
23+
} else {
24+
pythonBinary = path.Join(installDir, "python", "bin", "python3")
25+
pipBinary = path.Join(installDir, "python", "bin", "pip")
26+
}
27+
28+
return map[string]string{
29+
"installDir": installDir,
30+
"python": pythonBinary,
31+
"pip": pipBinary,
32+
}
33+
}
34+
35+
func getDownloadURL(pythonRuntime *Runtime) string {
36+
37+
version := pythonRuntime.Version()
38+
goos := runtime.GOOS
39+
goarch := runtime.GOARCH
40+
41+
var pyArch string
42+
switch goarch {
43+
case "386":
44+
pyArch = "x86"
45+
case "amd64":
46+
pyArch = "x86_64"
47+
case "arm":
48+
pyArch = "armv7l"
49+
case "arm64":
50+
pyArch = "aarch64"
51+
default:
52+
pyArch = goarch
53+
}
54+
55+
var pyOS string
56+
switch goos {
57+
case "darwin":
58+
pyOS = "apple-darwin"
59+
case "linux":
60+
pyOS = "unknown-linux-gnu"
61+
case "windows":
62+
pyOS = "pc-windows-msvc"
63+
default:
64+
pyOS = goos
65+
}
66+
67+
releaseVersion := "20250317"
68+
baseURL := "https://github.com/astral-sh/python-build-standalone/releases/download/"
69+
70+
filename := fmt.Sprintf("cpython-%s+%s-%s-%s-install_only.tar.gz", version, releaseVersion, pyArch, pyOS)
71+
72+
return fmt.Sprintf("%s%s/%s", baseURL, releaseVersion, filename)
73+
74+
}
75+
76+
func InstallPython(r *Runtime) error {
77+
78+
pythonFolder := fmt.Sprintf("%s@%s", r.Name(), r.Version())
79+
installDir := path.Join(Config.RuntimesDirectory(), pythonFolder)
80+
log.Println("Fetching python...")
81+
downloadPythonURL := getDownloadURL(r)
82+
pythonTar, err := utils.DownloadFile(downloadPythonURL, Config.RuntimesDirectory())
83+
if err != nil {
84+
return err
85+
}
86+
87+
// Make sure the installDir exists
88+
err = os.MkdirAll(installDir, 0777)
89+
if err != nil {
90+
return fmt.Errorf("failed to create install directory: %v", err)
91+
}
92+
93+
// Open the downloaded file
94+
t, err := os.Open(pythonTar)
95+
defer t.Close()
96+
if err != nil {
97+
return err
98+
}
99+
100+
// Extract the archive to the desired directory without creating links yet
101+
err = utils.ExtractTarGz(t, installDir)
102+
if err != nil {
103+
return fmt.Errorf("failed to extract archive: %v", err)
104+
}
105+
106+
//remove tar after extraction
107+
err = os.Remove(pythonTar)
108+
if err != nil {
109+
return fmt.Errorf("failed to delete downloaded archive: %v", err)
110+
}
111+
112+
log.Println("Python successfully installed at:", installDir)
113+
return nil
114+
}

config/runtime.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ func (r *Runtime) populateInfo() {
3232
r.info = genInfoNode(r)
3333
case "eslint":
3434
r.info = genInfoEslint(r)
35+
case "python":
36+
r.info = getInfoPython(r)
37+
case "pylint":
38+
r.info = getInfoPylint()
3539
}
3640
}
3741

tools/pylintRunner.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package tools
2+
3+
import (
4+
"bytes"
5+
"codacy/cli-v2/utils"
6+
"log"
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
)
11+
12+
func RunPylint(repositoryToAnalyseDirectory string, pylintInstallationDirectory string, pythonBinary string, pathsToCheck []string, autoFix bool, outputFile string) {
13+
14+
// Prepare the command to run pylint as a module with JSON output
15+
var args []string
16+
args = append(args, "-m", "pylint", "--output-format=json")
17+
18+
// Create a temporary file for JSON output if an output file is specified
19+
tempFile := ""
20+
if outputFile != "" {
21+
tempFile = filepath.Join(os.TempDir(), "pylint_output.json")
22+
args = append(args, "--output", tempFile)
23+
}
24+
25+
// Add files/directories to check
26+
if len(pathsToCheck) > 0 {
27+
args = append(args, pathsToCheck...)
28+
} else {
29+
args = append(args, repositoryToAnalyseDirectory)
30+
}
31+
32+
cmd := exec.Command(pythonBinary, args...)
33+
cmd.Dir = repositoryToAnalyseDirectory
34+
35+
// Set stderr and stdout to be displayed
36+
cmd.Stderr = os.Stderr
37+
38+
// For terminal output capture mode
39+
var stdout bytes.Buffer
40+
if outputFile == "" {
41+
// Terminal output mode - capture JSON and print SARIF directly
42+
cmd.Stdout = &stdout
43+
} else {
44+
// File output mode - show output in terminal
45+
cmd.Stdout = os.Stdout
46+
}
47+
48+
// Run pylint
49+
log.Printf("Running pylint command: %v", cmd.Args)
50+
// Pylint returns non-zero exit codes when it finds issues, so we're not checking the error
51+
cmd.Run()
52+
53+
if outputFile != "" {
54+
// Read the JSON output from the temporary file
55+
outputData, err := os.ReadFile(tempFile)
56+
if err != nil {
57+
log.Printf("Failed to read pylint output from %s: %v", tempFile, err)
58+
return
59+
}
60+
61+
// Delete temporary file
62+
defer os.Remove(tempFile)
63+
64+
// Convert JSON to SARIF using the utility function
65+
sarifData := utils.ConvertPylintToSarif(outputData)
66+
67+
// Write SARIF to the output file
68+
err = os.WriteFile(outputFile, sarifData, 0644)
69+
if err != nil {
70+
log.Printf("Failed to write SARIF output to %s: %v", outputFile, err)
71+
}
72+
73+
log.Printf("SARIF output saved to: %s\n", outputFile)
74+
} else {
75+
// Get the JSON output from the buffer
76+
jsonOutput := stdout.Bytes()
77+
78+
// Convert JSON to SARIF
79+
sarifOutput := utils.ConvertPylintToSarif(jsonOutput)
80+
81+
// Print the SARIF output to stdout
82+
os.Stdout.Write(sarifOutput)
83+
}
84+
}

0 commit comments

Comments
 (0)