@@ -3,10 +3,13 @@ package config
33import (
44 "bytes"
55 "codacy/cli-v2/plugins"
6+ "codacy/cli-v2/utils"
67 "fmt"
8+ "io"
79 "log"
810 "os"
911 "os/exec"
12+ "path/filepath"
1013 "strings"
1114 "text/template"
1215)
@@ -30,18 +33,26 @@ func InstallTool(name string, toolInfo *plugins.ToolInfo) error {
3033 return nil
3134 }
3235
33- // Get the runtime for this tool
34- runtimeInfo , ok := Config .Runtimes ()[toolInfo .Runtime ]
35- if ! ok {
36- return fmt .Errorf ("required runtime %s not found for tool %s" , toolInfo .Runtime , name )
37- }
38-
3936 // Make sure the installation directory exists
4037 err := os .MkdirAll (toolInfo .InstallDir , 0755 )
4138 if err != nil {
4239 return fmt .Errorf ("failed to create installation directory: %w" , err )
4340 }
4441
42+ // Check if this is a download-based tool (like trivy) or a runtime-based tool (like eslint)
43+ if toolInfo .DownloadURL != "" {
44+ // This is a download-based tool
45+ return installDownloadBasedTool (toolInfo )
46+ }
47+
48+ // This is a runtime-based tool, proceed with regular installation
49+
50+ // Get the runtime for this tool
51+ runtimeInfo , ok := Config .Runtimes ()[toolInfo .Runtime ]
52+ if ! ok {
53+ return fmt .Errorf ("required runtime %s not found for tool %s" , toolInfo .Runtime , name )
54+ }
55+
4556 // Prepare template data
4657 templateData := map [string ]string {
4758 "InstallDir" : toolInfo .InstallDir ,
@@ -80,7 +91,7 @@ func InstallTool(name string, toolInfo *plugins.ToolInfo) error {
8091
8192 // Execute the installation command using the package manager
8293 cmd := exec .Command (packageManagerBinary , strings .Split (installCmd , " " )... )
83-
94+
8495 log .Printf ("Installing %s v%s...\n " , toolInfo .Name , toolInfo .Version )
8596 output , err := cmd .CombinedOutput ()
8697 if err != nil {
@@ -91,6 +102,131 @@ func InstallTool(name string, toolInfo *plugins.ToolInfo) error {
91102 return nil
92103}
93104
105+ // installDownloadBasedTool installs a tool by downloading and extracting it
106+ func installDownloadBasedTool (toolInfo * plugins.ToolInfo ) error {
107+ // Create a file name for the downloaded archive
108+ fileName := filepath .Base (toolInfo .DownloadURL )
109+ downloadPath := filepath .Join (Config .ToolsDirectory (), fileName )
110+
111+ // Check if the file already exists
112+ _ , err := os .Stat (downloadPath )
113+ if os .IsNotExist (err ) {
114+ // Download the file
115+ log .Printf ("Downloading %s v%s...\n " , toolInfo .Name , toolInfo .Version )
116+ downloadPath , err = utils .DownloadFile (toolInfo .DownloadURL , Config .ToolsDirectory ())
117+ if err != nil {
118+ return fmt .Errorf ("failed to download tool: %w" , err )
119+ }
120+ } else if err != nil {
121+ return fmt .Errorf ("error checking for existing download: %w" , err )
122+ } else {
123+ log .Printf ("Using existing download for %s v%s\n " , toolInfo .Name , toolInfo .Version )
124+ }
125+
126+ // Open the downloaded file
127+ file , err := os .Open (downloadPath )
128+ if err != nil {
129+ return fmt .Errorf ("failed to open downloaded file: %w" , err )
130+ }
131+ defer file .Close ()
132+
133+ // Create a temporary extraction directory
134+ tempExtractDir := filepath .Join (Config .ToolsDirectory (), fmt .Sprintf ("%s-%s-temp" , toolInfo .Name , toolInfo .Version ))
135+
136+ // Clean up any previous extraction attempt
137+ os .RemoveAll (tempExtractDir )
138+
139+ // Create the temporary extraction directory
140+ err = os .MkdirAll (tempExtractDir , 0755 )
141+ if err != nil {
142+ return fmt .Errorf ("failed to create temporary extraction directory: %w" , err )
143+ }
144+
145+ // Extract to the temporary directory first
146+ log .Printf ("Extracting %s v%s...\n " , toolInfo .Name , toolInfo .Version )
147+ if strings .HasSuffix (fileName , ".zip" ) {
148+ err = utils .ExtractZip (file .Name (), tempExtractDir )
149+ } else {
150+ err = utils .ExtractTarGz (file , tempExtractDir )
151+ }
152+
153+ if err != nil {
154+ return fmt .Errorf ("failed to extract tool: %w" , err )
155+ }
156+
157+ // Create the final installation directory
158+ err = os .MkdirAll (toolInfo .InstallDir , 0755 )
159+ if err != nil {
160+ return fmt .Errorf ("failed to create installation directory: %w" , err )
161+ }
162+
163+ // Find and copy the tool binaries
164+ for binName , binPath := range toolInfo .Binaries {
165+ // Get the base name of the binary (without the path)
166+ binBaseName := filepath .Base (binPath )
167+
168+ // Try to find the binary in the extracted files
169+ foundPath := ""
170+
171+ // First check if it's at the expected location directly
172+ expectedPath := filepath .Join (tempExtractDir , binBaseName )
173+ if _ , err := os .Stat (expectedPath ); err == nil {
174+ foundPath = expectedPath
175+ } else {
176+ // Look for the binary anywhere in the extracted directory
177+ err := filepath .Walk (tempExtractDir , func (path string , info os.FileInfo , err error ) error {
178+ if err != nil {
179+ return err
180+ }
181+ if ! info .IsDir () && filepath .Base (path ) == binBaseName {
182+ foundPath = path
183+ return io .EOF // Stop the walk
184+ }
185+ return nil
186+ })
187+
188+ // io.EOF is expected when we find the file and stop the walk
189+ if err != nil && err != io .EOF {
190+ return fmt .Errorf ("error searching for %s binary: %w" , binName , err )
191+ }
192+ }
193+
194+ if foundPath == "" {
195+ return fmt .Errorf ("could not find %s binary in extracted files" , binName )
196+ }
197+
198+ // Make sure the destination directory exists
199+ err = os .MkdirAll (filepath .Dir (binPath ), 0755 )
200+ if err != nil {
201+ return fmt .Errorf ("failed to create directory for binary: %w" , err )
202+ }
203+
204+ // Copy the binary to the installation directory
205+ input , err := os .Open (foundPath )
206+ if err != nil {
207+ return fmt .Errorf ("failed to open %s binary: %w" , binName , err )
208+ }
209+ defer input .Close ()
210+
211+ output , err := os .OpenFile (binPath , os .O_RDWR | os .O_CREATE | os .O_TRUNC , 0755 )
212+ if err != nil {
213+ return fmt .Errorf ("failed to create destination file for %s: %w" , binName , err )
214+ }
215+ defer output .Close ()
216+
217+ _ , err = io .Copy (output , input )
218+ if err != nil {
219+ return fmt .Errorf ("failed to copy %s binary: %w" , binName , err )
220+ }
221+ }
222+
223+ // Clean up the temporary directory
224+ os .RemoveAll (tempExtractDir )
225+
226+ log .Printf ("Successfully installed %s v%s\n " , toolInfo .Name , toolInfo .Version )
227+ return nil
228+ }
229+
94230// isToolInstalled checks if a tool is already installed by checking for the binary
95231func isToolInstalled (toolInfo * plugins.ToolInfo ) bool {
96232 // If there are no binaries, check the install directory
@@ -116,12 +252,12 @@ func executeToolTemplate(tmplStr string, data map[string]string) (string, error)
116252 if err != nil {
117253 return "" , err
118254 }
119-
255+
120256 var buf bytes.Buffer
121257 err = tmpl .Execute (& buf , data )
122258 if err != nil {
123259 return "" , err
124260 }
125-
261+
126262 return buf .String (), nil
127263}
0 commit comments