2121import com .semmle .util .process .Env ;
2222import com .semmle .util .projectstructure .ProjectLayout ;
2323import com .semmle .util .trap .TrapWriter ;
24+ import java .io .BufferedReader ;
2425import java .io .File ;
2526import java .io .IOException ;
27+ import java .io .InputStreamReader ;
2628import java .io .Reader ;
2729import java .lang .ProcessBuilder .Redirect ;
2830import java .net .URI ;
@@ -195,6 +197,11 @@ public class AutoBuild {
195197 private final String defaultEncoding ;
196198 private ExecutorService threadPool ;
197199 private volatile boolean seenCode = false ;
200+ private boolean installDependencies = false ;
201+ private int installDependenciesTimeout ;
202+
203+ /** The default timeout when running <code>yarn</code>, in milliseconds. */
204+ public static final int INSTALL_DEPENDENCIES_DEFAULT_TIMEOUT = 10 * 60 * 1000 ; // 10 minutes
198205
199206 public AutoBuild () {
200207 this .LGTM_SRC = toRealPath (getPathFromEnvVar ("LGTM_SRC" ));
@@ -204,6 +211,11 @@ public AutoBuild() {
204211 this .typeScriptMode =
205212 getEnumFromEnvVar ("LGTM_INDEX_TYPESCRIPT" , TypeScriptMode .class , TypeScriptMode .FULL );
206213 this .defaultEncoding = getEnvVar ("LGTM_INDEX_DEFAULT_ENCODING" );
214+ this .installDependencies = Boolean .valueOf (getEnvVar ("LGTM_INDEX_TYPESCRIPT_INSTALL_DEPS" ));
215+ this .installDependenciesTimeout =
216+ Env .systemEnv ()
217+ .getInt (
218+ "LGTM_INDEX_TYPESCRIPT_INSTALL_DEPS_TIMEOUT" , INSTALL_DEPENDENCIES_DEFAULT_TIMEOUT );
207219 setupFileTypes ();
208220 setupXmlMode ();
209221 setupMatchers ();
@@ -533,6 +545,10 @@ private void extractSource() throws IOException {
533545 List <Path > tsconfigFiles = new ArrayList <>();
534546 findFilesToExtract (defaultExtractor , filesToExtract , tsconfigFiles );
535547
548+ if (!tsconfigFiles .isEmpty () && this .installDependencies ) {
549+ this .installDependencies (filesToExtract );
550+ }
551+
536552 // extract TypeScript projects and files
537553 Set <Path > extractedFiles = extractTypeScript (defaultExtractor , filesToExtract , tsconfigFiles );
538554
@@ -549,6 +565,61 @@ private void extractSource() throws IOException {
549565 }
550566 }
551567
568+ /** Returns true if yarn is installed, otherwise prints a warning and returns false. */
569+ private boolean verifyYarnInstallation () {
570+ ProcessBuilder pb = new ProcessBuilder (Arrays .asList ("yarn" , "-v" ));
571+ try {
572+ Process process = pb .start ();
573+ boolean completed = process .waitFor (this .installDependenciesTimeout , TimeUnit .MILLISECONDS );
574+ if (!completed ) {
575+ System .err .println ("Yarn could not be launched. Timeout during 'yarn -v'." );
576+ return false ;
577+ }
578+ BufferedReader reader = new BufferedReader (new InputStreamReader (process .getInputStream ()));
579+ String version = reader .readLine ();
580+ System .out .println ("Found yarn version: " + version );
581+ return true ;
582+ } catch (IOException | InterruptedException ex ) {
583+ System .err .println (
584+ "Yarn not found. Please put 'yarn' on the PATH for automatic dependency installation." );
585+ Exceptions .ignore (ex , "Continue without dependency installation" );
586+ return false ;
587+ }
588+ }
589+
590+ protected void installDependencies (Set <Path > filesToExtract ) {
591+ if (!verifyYarnInstallation ()) {
592+ return ;
593+ }
594+ for (Path file : filesToExtract ) {
595+ if (file .getFileName ().toString ().equals ("package.json" )) {
596+ System .out .println ("Installing dependencies from " + file );
597+ ProcessBuilder pb =
598+ new ProcessBuilder (
599+ Arrays .asList (
600+ "yarn" ,
601+ "install" ,
602+ "--verbose" ,
603+ "--non-interactive" ,
604+ "--ignore-scripts" ,
605+ "--ignore-platform" ,
606+ "--ignore-engines" ,
607+ "--ignore-optional" ,
608+ "--no-default-rc" ,
609+ "--no-bin-links" ,
610+ "--pure-lockfile" ));
611+ pb .directory (file .getParent ().toFile ());
612+ pb .redirectOutput (Redirect .INHERIT );
613+ pb .redirectError (Redirect .INHERIT );
614+ try {
615+ pb .start ().waitFor (this .installDependenciesTimeout , TimeUnit .MILLISECONDS );
616+ } catch (IOException | InterruptedException ex ) {
617+ throw new ResourceError ("Could not install dependencies from " + file , ex );
618+ }
619+ }
620+ }
621+ }
622+
552623 private ExtractorConfig mkExtractorConfig () {
553624 ExtractorConfig config = new ExtractorConfig (true );
554625 config = config .withSourceType (getSourceType ());
0 commit comments