xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/index/Indexer.java (revision 059d25e52724652d1b991e7e17ffc72e2bf402fa)
1b5840353SAdam Hornáček /*
2b5840353SAdam Hornáček  * CDDL HEADER START
3b5840353SAdam Hornáček  *
4b5840353SAdam Hornáček  * The contents of this file are subject to the terms of the
5b5840353SAdam Hornáček  * Common Development and Distribution License (the "License").
6b5840353SAdam Hornáček  * You may not use this file except in compliance with the License.
7b5840353SAdam Hornáček  *
8b5840353SAdam Hornáček  * See LICENSE.txt included in this distribution for the specific
9b5840353SAdam Hornáček  * language governing permissions and limitations under the License.
10b5840353SAdam Hornáček  *
11b5840353SAdam Hornáček  * When distributing Covered Code, include this CDDL HEADER in each
12b5840353SAdam Hornáček  * file and include the License file at LICENSE.txt.
13b5840353SAdam Hornáček  * If applicable, add the following below this CDDL HEADER, with the
14b5840353SAdam Hornáček  * fields enclosed by brackets "[]" replaced with your own identifying
15b5840353SAdam Hornáček  * information: Portions Copyright [yyyy] [name of copyright owner]
16b5840353SAdam Hornáček  *
17b5840353SAdam Hornáček  * CDDL HEADER END
18b5840353SAdam Hornáček  */
19b5840353SAdam Hornáček 
20b5840353SAdam Hornáček /*
211b935619SVladimir Kotal  * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved.
225d9f3aa0SAdam Hornáček  * Portions Copyright (c) 2011, Jens Elkner.
235d9f3aa0SAdam Hornáček  * Portions Copyright (c) 2017, 2020, Chris Fraire <cfraire@me.com>.
24b5840353SAdam Hornáček  */
259805b761SAdam Hornáček package org.opengrok.indexer.index;
26b5840353SAdam Hornáček 
27195a23a5SVladimir Kotal import java.io.BufferedReader;
28b5840353SAdam Hornáček import java.io.File;
29195a23a5SVladimir Kotal import java.io.FileInputStream;
30b5840353SAdam Hornáček import java.io.IOException;
31195a23a5SVladimir Kotal import java.io.InputStreamReader;
3262b82a8dSChris Fraire import java.io.PrintStream;
3387a275e9SAnatoly Akkerman import java.io.UncheckedIOException;
34b5840353SAdam Hornáček import java.lang.reflect.Field;
35b5840353SAdam Hornáček import java.lang.reflect.InvocationTargetException;
36fa2acf12SVladimir Kotal import java.net.URI;
37fa2acf12SVladimir Kotal import java.net.URISyntaxException;
3887a275e9SAnatoly Akkerman import java.nio.file.Files;
3987a275e9SAnatoly Akkerman import java.nio.file.Path;
40b5840353SAdam Hornáček import java.nio.file.Paths;
41b5840353SAdam Hornáček import java.text.ParseException;
42b5840353SAdam Hornáček import java.util.ArrayList;
4311cc45afSVladimir Kotal import java.util.Arrays;
4487a275e9SAnatoly Akkerman import java.util.Collection;
45937bd40dSVladimir Kotal import java.util.Collections;
46b5840353SAdam Hornáček import java.util.HashMap;
47b5840353SAdam Hornáček import java.util.HashSet;
48b5840353SAdam Hornáček import java.util.List;
49b5840353SAdam Hornáček import java.util.Locale;
50b5840353SAdam Hornáček import java.util.Map;
51b5840353SAdam Hornáček import java.util.Map.Entry;
52b5840353SAdam Hornáček import java.util.Scanner;
53b5840353SAdam Hornáček import java.util.Set;
54b5840353SAdam Hornáček import java.util.TreeSet;
55e829566cSChris Fraire import java.util.concurrent.CountDownLatch;
56b5840353SAdam Hornáček import java.util.logging.Level;
57b5840353SAdam Hornáček import java.util.logging.Logger;
58b5840353SAdam Hornáček import java.util.stream.Collectors;
590d7ace53SVladimir Kotal 
606c62ede9SAdam Hornacek import org.apache.commons.lang3.SystemUtils;
615a8ab20bSAdam Hornáček import org.opengrok.indexer.Info;
620d7ace53SVladimir Kotal import org.opengrok.indexer.Metrics;
639805b761SAdam Hornáček import org.opengrok.indexer.analysis.AnalyzerGuru;
649805b761SAdam Hornáček import org.opengrok.indexer.analysis.AnalyzerGuruHelp;
652d875ddeSChris Fraire import org.opengrok.indexer.analysis.Ctags;
66fcbdc96bSChris Fraire import org.opengrok.indexer.configuration.CanonicalRootValidator;
67a904b83aSVladimir Kotal import org.opengrok.indexer.configuration.CommandTimeoutType;
689805b761SAdam Hornáček import org.opengrok.indexer.configuration.Configuration;
699805b761SAdam Hornáček import org.opengrok.indexer.configuration.ConfigurationHelp;
709805b761SAdam Hornáček import org.opengrok.indexer.configuration.LuceneLockName;
719805b761SAdam Hornáček import org.opengrok.indexer.configuration.Project;
729805b761SAdam Hornáček import org.opengrok.indexer.configuration.RuntimeEnvironment;
739805b761SAdam Hornáček import org.opengrok.indexer.history.HistoryGuru;
74cfbc0ba5SChris Fraire import org.opengrok.indexer.history.RepositoriesHelp;
759805b761SAdam Hornáček import org.opengrok.indexer.history.Repository;
769805b761SAdam Hornáček import org.opengrok.indexer.history.RepositoryFactory;
779805b761SAdam Hornáček import org.opengrok.indexer.history.RepositoryInfo;
789805b761SAdam Hornáček import org.opengrok.indexer.logger.LoggerFactory;
799805b761SAdam Hornáček import org.opengrok.indexer.logger.LoggerUtil;
802d875ddeSChris Fraire import org.opengrok.indexer.util.CtagsUtil;
819805b761SAdam Hornáček import org.opengrok.indexer.util.Executor;
825ea61d18SVladimir Kotal import org.opengrok.indexer.util.HostUtil;
839805b761SAdam Hornáček import org.opengrok.indexer.util.OptionParser;
849805b761SAdam Hornáček import org.opengrok.indexer.util.Statistics;
85b5840353SAdam Hornáček 
86b5840353SAdam Hornáček /**
87b5840353SAdam Hornáček  * Creates and updates an inverted source index as well as generates Xref, file
88ff44f24aSAdam Hornáček  * stats etc., if specified in the options.
89807ead8fSLubos Kosco  *
90807ead8fSLubos Kosco  * We shall use / as path delimiter in whole opengrok for uuids and paths
91807ead8fSLubos Kosco  * from Windows systems, the path shall be converted when entering the index or web
92807ead8fSLubos Kosco  * and converted back if needed* to access original file
93807ead8fSLubos Kosco  *
94807ead8fSLubos Kosco  * *Windows already supports opening /var/opengrok as C:\var\opengrok
95b5840353SAdam Hornáček  */
96b5840353SAdam Hornáček @SuppressWarnings({"PMD.AvoidPrintStackTrace", "PMD.SystemPrintln"})
97b5840353SAdam Hornáček public final class Indexer {
98b5840353SAdam Hornáček 
99b5840353SAdam Hornáček     private static final Logger LOGGER = LoggerFactory.getLogger(Indexer.class);
100b5840353SAdam Hornáček 
101b5840353SAdam Hornáček     /* tunables for -r (history for remote repositories) */
102b5840353SAdam Hornáček     private static final String ON = "on";
103b5840353SAdam Hornáček     private static final String OFF = "off";
104b5840353SAdam Hornáček     private static final String DIRBASED = "dirbased";
105b5840353SAdam Hornáček     private static final String UIONLY = "uionly";
106b5840353SAdam Hornáček 
107807ead8fSLubos Kosco     //whole app uses this separator
108807ead8fSLubos Kosco     public static final char PATH_SEPARATOR = '/';
10920463cfeSChris Fraire     public static final String PATH_SEPARATOR_STRING = Character.toString(PATH_SEPARATOR);
110807ead8fSLubos Kosco 
111a4876aa6SChris Fraire     private static final String HELP_OPT_1 = "--help";
112a4876aa6SChris Fraire     private static final String HELP_OPT_2 = "-?";
113a4876aa6SChris Fraire     private static final String HELP_OPT_3 = "-h";
114a4876aa6SChris Fraire 
1152d8cba21SVladimir Kotal     private static final Indexer indexer = new Indexer();
116b5840353SAdam Hornáček     private static Configuration cfg = null;
1171277512bSVladimir Kotal     private static boolean checkIndex = false;
118b5840353SAdam Hornáček     private static boolean runIndex = true;
119b5840353SAdam Hornáček     private static boolean optimizedChanged = false;
120b5840353SAdam Hornáček     private static boolean addProjects = false;
121b5840353SAdam Hornáček     private static boolean searchRepositories = false;
1228ea3083fSVladimir Kotal     private static boolean bareConfig = false;
123b5840353SAdam Hornáček     private static boolean awaitProfiler;
124b5840353SAdam Hornáček 
1255e426b1cSChris Fraire     private static boolean help;
126b5840353SAdam Hornáček     private static String helpUsage;
1275e426b1cSChris Fraire     private static HelpMode helpMode = HelpMode.DEFAULT;
128b5840353SAdam Hornáček 
129b5840353SAdam Hornáček     private static String configFilename = null;
130b5840353SAdam Hornáček     private static int status = 0;
131b5840353SAdam Hornáček 
13222478587SVladimir Kotal     private static final Set<String> repositories = new HashSet<>();
133937bd40dSVladimir Kotal     private static Set<String> searchPaths = new HashSet<>();
134b5840353SAdam Hornáček     private static final HashSet<String> allowedSymlinks = new HashSet<>();
1350e0ac58dSChris Fraire     private static final HashSet<String> canonicalRoots = new HashSet<>();
136b5840353SAdam Hornáček     private static final Set<String> defaultProjects = new TreeSet<>();
13729816c3bSChris Fraire     private static final HashSet<String> disabledRepositories = new HashSet<>();
138b5840353SAdam Hornáček     private static RuntimeEnvironment env = null;
13955402125SVladimir Kotal     private static String webappURI = null;
140b5840353SAdam Hornáček 
14146414f58SVladimir Kotal     private static OptionParser optParser = null;
14283adc92cSVladimir Kotal     private static boolean verbose = false;
143b5840353SAdam Hornáček 
1447e5adef9SVladimir Kotal     private static final String[] ON_OFF = {ON, OFF};
1457e5adef9SVladimir Kotal     private static final String[] REMOTE_REPO_CHOICES = {ON, OFF, DIRBASED, UIONLY};
1467e5adef9SVladimir Kotal     private static final String[] LUCENE_LOCKS = {ON, OFF, "simple", "native"};
1477e5adef9SVladimir Kotal     private static final String OPENGROK_JAR = "opengrok.jar";
1487e5adef9SVladimir Kotal 
1497e5adef9SVladimir Kotal     private static final int WEBAPP_CONNECT_TIMEOUT = 1000;  // in milliseconds
1507e5adef9SVladimir Kotal 
getInstance()151b5840353SAdam Hornáček     public static Indexer getInstance() {
1522d8cba21SVladimir Kotal         return indexer;
153b5840353SAdam Hornáček     }
154b5840353SAdam Hornáček 
155b5840353SAdam Hornáček     /**
156ff44f24aSAdam Hornáček      * Program entry point.
157b5840353SAdam Hornáček      *
158b5840353SAdam Hornáček      * @param argv argument vector
159b5840353SAdam Hornáček      */
160b5840353SAdam Hornáček     @SuppressWarnings("PMD.UseStringBufferForStringAppends")
main(String[] argv)161d1e826faSAdam Hornáček     public static void main(String[] argv) {
162b5840353SAdam Hornáček         Statistics stats = new Statistics(); //this won't count JVM creation though
163b5840353SAdam Hornáček         boolean update = true;
164b5840353SAdam Hornáček 
165b5840353SAdam Hornáček         Executor.registerErrorHandler();
1660d7ace53SVladimir Kotal         List<String> subFiles = RuntimeEnvironment.getInstance().getSubFiles();
1671b935619SVladimir Kotal         Set<String> subFilesArgs = new HashSet<>();
168b5840353SAdam Hornáček 
169b5840353SAdam Hornáček         boolean createDict = false;
170b5840353SAdam Hornáček 
171b5840353SAdam Hornáček         try {
172b5840353SAdam Hornáček             argv = parseOptions(argv);
173982b7b22SChris Fraire 
1747e5adef9SVladimir Kotal             if (webappURI != null && !HostUtil.isReachable(webappURI, WEBAPP_CONNECT_TIMEOUT)) {
1755ea61d18SVladimir Kotal                 System.err.println(webappURI + " is not reachable.");
1765ea61d18SVladimir Kotal                 System.exit(1);
1775ea61d18SVladimir Kotal             }
1785ea61d18SVladimir Kotal 
179982b7b22SChris Fraire             /*
180982b7b22SChris Fraire              * Attend to disabledRepositories here in case exitWithHelp() will
181982b7b22SChris Fraire              * need to report about repos.
182982b7b22SChris Fraire              */
183982b7b22SChris Fraire             disabledRepositories.addAll(cfg.getDisabledRepositories());
184982b7b22SChris Fraire             cfg.setDisabledRepositories(disabledRepositories);
185982b7b22SChris Fraire             for (String repoName : disabledRepositories) {
186982b7b22SChris Fraire                 LOGGER.log(Level.FINEST, "Disabled {0}", repoName);
187982b7b22SChris Fraire             }
188982b7b22SChris Fraire 
1895e426b1cSChris Fraire             if (help) {
190f5124002SChris Fraire                 exitWithHelp();
191b5840353SAdam Hornáček             }
192b5840353SAdam Hornáček 
19322478587SVladimir Kotal             checkConfiguration();
19422478587SVladimir Kotal 
195a72324b1SAdam Hornáček             if (awaitProfiler) {
196a72324b1SAdam Hornáček                 pauseToAwaitProfiler();
197a72324b1SAdam Hornáček             }
198b5840353SAdam Hornáček 
199b5840353SAdam Hornáček             env = RuntimeEnvironment.getInstance();
2000d7ace53SVladimir Kotal             env.setIndexer(true);
201b5840353SAdam Hornáček 
202b5840353SAdam Hornáček             // Complete the configuration of repository types.
203b8ad1421SVladimir Kotal             List<Class<? extends Repository>> repositoryClasses = RepositoryFactory.getRepositoryClasses();
204b5840353SAdam Hornáček             for (Class<? extends Repository> clazz : repositoryClasses) {
205b5840353SAdam Hornáček                 // Set external repository binaries from System properties.
206b5840353SAdam Hornáček                 try {
207b5840353SAdam Hornáček                     Field f = clazz.getDeclaredField("CMD_PROPERTY_KEY");
208b5840353SAdam Hornáček                     Object key = f.get(null);
209b5840353SAdam Hornáček                     if (key != null) {
210b5840353SAdam Hornáček                         cfg.setRepoCmd(clazz.getCanonicalName(),
211b5840353SAdam Hornáček                                 System.getProperty(key.toString()));
212b5840353SAdam Hornáček                     }
213b5840353SAdam Hornáček                 } catch (Exception e) {
214b5840353SAdam Hornáček                     // don't care
215b5840353SAdam Hornáček                 }
216b5840353SAdam Hornáček             }
217b5840353SAdam Hornáček 
218b5840353SAdam Hornáček             // Logging starts here.
21983adc92cSVladimir Kotal             if (verbose) {
220b5840353SAdam Hornáček                 String fn = LoggerUtil.getFileHandlerPattern();
221b5840353SAdam Hornáček                 if (fn != null) {
222b5840353SAdam Hornáček                     System.out.println("Logging filehandler pattern: " + fn);
223b5840353SAdam Hornáček                 }
224b5840353SAdam Hornáček             }
225b5840353SAdam Hornáček 
226b5840353SAdam Hornáček             // automatically allow symlinks that are directly in source root
227d1d942baSAdam Hornacek             File sourceRootFile = new File(cfg.getSourceRoot());
228b5840353SAdam Hornáček             File[] projectDirs = sourceRootFile.listFiles();
229b5840353SAdam Hornáček             if (projectDirs != null) {
230b5840353SAdam Hornáček                 for (File projectDir : projectDirs) {
231b5840353SAdam Hornáček                     if (!projectDir.getCanonicalPath().equals(projectDir.getAbsolutePath())) {
232b5840353SAdam Hornáček                         allowedSymlinks.add(projectDir.getAbsolutePath());
233b5840353SAdam Hornáček                     }
234b5840353SAdam Hornáček                 }
235b5840353SAdam Hornáček             }
236b5840353SAdam Hornáček 
237b5840353SAdam Hornáček             allowedSymlinks.addAll(cfg.getAllowedSymlinks());
238b5840353SAdam Hornáček             cfg.setAllowedSymlinks(allowedSymlinks);
239b5840353SAdam Hornáček 
2400e0ac58dSChris Fraire             canonicalRoots.addAll(cfg.getCanonicalRoots());
2410e0ac58dSChris Fraire             cfg.setCanonicalRoots(canonicalRoots);
2420e0ac58dSChris Fraire 
2437e5adef9SVladimir Kotal             // Assemble the unprocessed command line arguments (possibly a list of paths).
2441b935619SVladimir Kotal             // This will be used to perform more fine-grained checking in invalidateRepositories()
2451b935619SVladimir Kotal             // called from the setConfiguration() below.
246a72324b1SAdam Hornáček             for (String arg : argv) {
247a72324b1SAdam Hornáček                 String path = Paths.get(cfg.getSourceRoot(), arg).toString();
2481b935619SVladimir Kotal                 subFilesArgs.add(path);
249b5840353SAdam Hornáček             }
250b5840353SAdam Hornáček 
2517e5adef9SVladimir Kotal             // If a user used customizations for projects he perhaps just
252b5840353SAdam Hornáček             // used the key value for project without a name but the code
2537e5adef9SVladimir Kotal             // expects a name for the project. Therefore, we fill the name
254b5840353SAdam Hornáček             // according to the project key which is the same.
255b5840353SAdam Hornáček             for (Entry<String, Project> entry : cfg.getProjects().entrySet()) {
256b5840353SAdam Hornáček                 if (entry.getValue().getName() == null) {
257b5840353SAdam Hornáček                     entry.getValue().setName(entry.getKey());
258b5840353SAdam Hornáček                 }
259b5840353SAdam Hornáček             }
260b5840353SAdam Hornáček 
2612ffbb0cfSVladimir Kotal             // Check version of index(es) versus current Lucene version and exit
2622ffbb0cfSVladimir Kotal             // with return code upon failure.
2631277512bSVladimir Kotal             if (checkIndex) {
2647e5adef9SVladimir Kotal                 if (cfg.getDataRoot() == null || cfg.getDataRoot().isEmpty()) {
2657e5adef9SVladimir Kotal                     System.err.println("Need data root in configuration for index check (use -R)");
2662ffbb0cfSVladimir Kotal                     System.exit(1);
2672ffbb0cfSVladimir Kotal                 }
2682ffbb0cfSVladimir Kotal 
2691b935619SVladimir Kotal                 if (!IndexCheck.check(cfg, subFilesArgs)) {
270c6f0939bSAdam Hornacek                     System.err.printf("Index check failed%n");
271786b9f4bSChris Fraire                     System.err.print("You might want to remove " +
2721b935619SVladimir Kotal                             (!subFilesArgs.isEmpty() ? "data for projects " + String.join(",", subFilesArgs) :
273311d83f9SVladimir Kotal                                     "all data") + " under the data root and reindex\n");
2741277512bSVladimir Kotal                     System.exit(1);
2752ffbb0cfSVladimir Kotal                 }
276f750ae73SVladimir Kotal 
277f750ae73SVladimir Kotal                 System.exit(0);
2782ffbb0cfSVladimir Kotal             }
2792ffbb0cfSVladimir Kotal 
280b8ad1421SVladimir Kotal             // Set updated configuration in RuntimeEnvironment. This is called so that the tunables set
281b8ad1421SVladimir Kotal             // via command line options are available.
2821b935619SVladimir Kotal             env.setConfiguration(cfg, subFilesArgs, CommandTimeoutType.INDEXER);
283552e80caSAdam Hornacek 
284b5840353SAdam Hornáček             // Let repository types to add items to ignoredNames.
285b5840353SAdam Hornáček             // This changes env so is called after the setConfiguration()
286b5840353SAdam Hornáček             // call above.
287b5840353SAdam Hornáček             RepositoryFactory.initializeIgnoredNames(env);
288b5840353SAdam Hornáček 
2898ea3083fSVladimir Kotal             if (bareConfig) {
290b8ad1421SVladimir Kotal                 // Set updated configuration in RuntimeEnvironment.
291b8ad1421SVladimir Kotal                 env.setConfiguration(cfg, subFilesArgs, CommandTimeoutType.INDEXER);
292b8ad1421SVladimir Kotal 
29355402125SVladimir Kotal                 getInstance().sendToConfigHost(env, webappURI);
294b5840353SAdam Hornáček                 writeConfigToFile(env, configFilename);
295b5840353SAdam Hornáček                 System.exit(0);
296b5840353SAdam Hornáček             }
297b5840353SAdam Hornáček 
298b5840353SAdam Hornáček             /*
299b5840353SAdam Hornáček              * Add paths to directories under source root. If projects
300b5840353SAdam Hornáček              * are enabled the path should correspond to a project because
301b5840353SAdam Hornáček              * project path is necessary to correctly set index directory
302b5840353SAdam Hornáček              * (otherwise the index files will end up in index data root
303b5840353SAdam Hornáček              * directory and not per project data root directory).
304b5840353SAdam Hornáček              * For the check we need to have 'env' already set.
305b5840353SAdam Hornáček              */
3061b935619SVladimir Kotal             for (String path : subFilesArgs) {
307b5840353SAdam Hornáček                 String srcPath = env.getSourceRootPath();
308b5840353SAdam Hornáček                 if (srcPath == null) {
309b5840353SAdam Hornáček                     System.err.println("Error getting source root from environment. Exiting.");
310b5840353SAdam Hornáček                     System.exit(1);
311b5840353SAdam Hornáček                 }
312b5840353SAdam Hornáček 
313b5840353SAdam Hornáček                 path = path.substring(srcPath.length());
314b5840353SAdam Hornáček                 if (env.hasProjects()) {
315b5840353SAdam Hornáček                     // The paths need to correspond to a project.
31622478587SVladimir Kotal                     Project project;
31722478587SVladimir Kotal                     if ((project = Project.getProject(path)) != null) {
318b5840353SAdam Hornáček                         subFiles.add(path);
319eb782d75SVladimir Kotal                         List<RepositoryInfo> repoList = env.getProjectRepositoriesMap().get(project);
320eb782d75SVladimir Kotal                         if (repoList != null) {
321eb782d75SVladimir Kotal                             repositories.addAll(repoList.
322ff44f24aSAdam Hornáček                                     stream().map(RepositoryInfo::getDirectoryNameRelative).collect(Collectors.toSet()));
323eb782d75SVladimir Kotal                         }
324b5840353SAdam Hornáček                     } else {
325b5840353SAdam Hornáček                         System.err.println("The path " + path
326b5840353SAdam Hornáček                                 + " does not correspond to a project");
327b5840353SAdam Hornáček                     }
328b5840353SAdam Hornáček                 } else {
329b5840353SAdam Hornáček                     subFiles.add(path);
330b5840353SAdam Hornáček                 }
331b5840353SAdam Hornáček             }
332b5840353SAdam Hornáček 
3331b935619SVladimir Kotal             if (!subFilesArgs.isEmpty() && subFiles.isEmpty()) {
334b5840353SAdam Hornáček                 System.err.println("None of the paths were added, exiting");
335b5840353SAdam Hornáček                 System.exit(1);
336b5840353SAdam Hornáček             }
337b5840353SAdam Hornáček 
3384be11797SVladimir Kotal             if (!subFiles.isEmpty() && configFilename != null) {
3394be11797SVladimir Kotal                 LOGGER.log(Level.WARNING, "The collection of entries to process is non empty ({0}), seems like " +
3404be11797SVladimir Kotal                         "the intention is to perform per project reindex, however the -W option is used. " +
3414be11797SVladimir Kotal                         "This will likely not work.", subFiles);
3424be11797SVladimir Kotal             }
3434be11797SVladimir Kotal 
3440d7ace53SVladimir Kotal             Metrics.updateSubFiles(subFiles);
3450d7ace53SVladimir Kotal 
346b5840353SAdam Hornáček             // If the webapp is running with a config that does not contain
347b5840353SAdam Hornáček             // 'projectsEnabled' property (case of upgrade or transition
348b5840353SAdam Hornáček             // from project-less config to one with projects), set the property
3494ce4e2b9SAdam Hornáček             // so that the 'project/indexed' messages
350b5840353SAdam Hornáček             // emitted during indexing do not cause validation error.
35155402125SVladimir Kotal             if (addProjects && webappURI != null) {
352b5840353SAdam Hornáček                 try {
35355402125SVladimir Kotal                     IndexerUtil.enableProjects(webappURI);
3544ce4e2b9SAdam Hornáček                 } catch (Exception e) {
3555fc870e4SKryštof Tulinger                     LOGGER.log(Level.SEVERE, String.format("Couldn't notify the webapp on %s.", webappURI), e);
356c6f0939bSAdam Hornacek                     System.err.printf("Couldn't notify the webapp on %s: %s.%n", webappURI, e.getLocalizedMessage());
357b5840353SAdam Hornáček                 }
358b5840353SAdam Hornáček             }
359b5840353SAdam Hornáček 
36010670d68SVladimir Kotal             LOGGER.log(Level.INFO, "Indexer version {0} ({1}) running on Java {2}",
36110670d68SVladimir Kotal                     new Object[]{Info.getVersion(), Info.getRevision(), System.getProperty("java.version")});
362b5840353SAdam Hornáček 
36318d0c95fSVladimir Kotal             // Create history cache first.
364937bd40dSVladimir Kotal             if (searchRepositories) {
365937bd40dSVladimir Kotal                 if (searchPaths.isEmpty()) {
3668df660a0SVladimir Kotal                     String[] dirs = env.getSourceRootFile().
3678df660a0SVladimir Kotal                             list((f, name) -> f.isDirectory() && env.getPathAccepter().accept(f));
3688df660a0SVladimir Kotal                     if (dirs != null) {
3698df660a0SVladimir Kotal                         searchPaths.addAll(Arrays.asList(dirs));
3708df660a0SVladimir Kotal                     }
3718df660a0SVladimir Kotal                 }
3728df660a0SVladimir Kotal 
373937bd40dSVladimir Kotal                 searchPaths = searchPaths.stream().
374937bd40dSVladimir Kotal                         map(t -> Paths.get(env.getSourceRootPath(), t).toString()).
375937bd40dSVladimir Kotal                         collect(Collectors.toSet());
376937bd40dSVladimir Kotal             }
377937bd40dSVladimir Kotal             getInstance().prepareIndexer(env, searchPaths, addProjects,
3787516a8e8SVladimir Kotal                     createDict, runIndex, subFiles, new ArrayList<>(repositories));
379b5840353SAdam Hornáček 
380b8ad1421SVladimir Kotal             // Set updated configuration in RuntimeEnvironment. This is called so that repositories discovered
381b8ad1421SVladimir Kotal             // in prepareIndexer() are stored in the Configuration used by RuntimeEnvironment.
382b8ad1421SVladimir Kotal             env.setConfiguration(cfg, subFilesArgs, CommandTimeoutType.INDEXER);
383b8ad1421SVladimir Kotal 
384369f83fbSVladimir Kotal             // prepareIndexer() populated the list of projects so now default projects can be set.
385369f83fbSVladimir Kotal             env.setDefaultProjectsFromNames(defaultProjects);
386369f83fbSVladimir Kotal 
387b5840353SAdam Hornáček             // And now index it all.
388b5840353SAdam Hornáček             if (runIndex || (optimizedChanged && env.isOptimizeDatabase())) {
389b5840353SAdam Hornáček                 IndexChangedListener progress = new DefaultIndexChangedListener();
390b5840353SAdam Hornáček                 getInstance().doIndexerExecution(update, subFiles, progress);
391b5840353SAdam Hornáček             }
392b5840353SAdam Hornáček 
393b5840353SAdam Hornáček             writeConfigToFile(env, configFilename);
394b5840353SAdam Hornáček 
395fd1c3630SVladimir Kotal             // Finally, send new configuration to the web application in the case of full reindex.
396fd1c3630SVladimir Kotal             if (webappURI != null && subFiles.isEmpty()) {
39755402125SVladimir Kotal                 getInstance().sendToConfigHost(env, webappURI);
398b5840353SAdam Hornáček             }
399b5840353SAdam Hornáček 
400e829566cSChris Fraire             env.getIndexerParallelizer().bounce();
401b5840353SAdam Hornáček         } catch (ParseException e) {
402b5840353SAdam Hornáček             System.err.println("** " + e.getMessage());
403b5840353SAdam Hornáček             System.exit(1);
404b5840353SAdam Hornáček         } catch (IndexerException ex) {
405b5840353SAdam Hornáček             LOGGER.log(Level.SEVERE, "Exception running indexer", ex);
4068426dcf4SKryštof Tulinger             System.err.println("Exception: " + ex.getLocalizedMessage());
40746414f58SVladimir Kotal             System.err.println(optParser.getUsage());
408b5840353SAdam Hornáček             System.exit(1);
409b5840353SAdam Hornáček         } catch (Throwable e) {
410b5840353SAdam Hornáček             LOGGER.log(Level.SEVERE, "Unexpected Exception", e);
411b5840353SAdam Hornáček             System.err.println("Exception: " + e.getLocalizedMessage());
412b5840353SAdam Hornáček             System.exit(1);
413b5840353SAdam Hornáček         } finally {
4140d7ace53SVladimir Kotal             stats.report(LOGGER, "Indexer finished", "indexer.total");
415b5840353SAdam Hornáček         }
416b5840353SAdam Hornáček     }
417b5840353SAdam Hornáček 
checkConfiguration()41861dce4cbSVladimir Kotal     private static void checkConfiguration() {
41961dce4cbSVladimir Kotal         if (bareConfig && (env.getConfigURI() == null || env.getConfigURI().isEmpty())) {
42061dce4cbSVladimir Kotal             die("Missing webappURI setting");
42161dce4cbSVladimir Kotal         }
42261dce4cbSVladimir Kotal 
42361dce4cbSVladimir Kotal         if (!repositories.isEmpty() && !cfg.isHistoryEnabled()) {
42461dce4cbSVladimir Kotal             die("Repositories were specified; history is off however");
42561dce4cbSVladimir Kotal         }
42661dce4cbSVladimir Kotal 
42761dce4cbSVladimir Kotal         try {
42861dce4cbSVladimir Kotal             cfg.checkConfiguration();
42961dce4cbSVladimir Kotal         } catch (Configuration.ConfigurationException e) {
43061dce4cbSVladimir Kotal             die(e.getMessage());
43161dce4cbSVladimir Kotal         }
43261dce4cbSVladimir Kotal     }
43361dce4cbSVladimir Kotal 
434b5840353SAdam Hornáček     /**
435b5840353SAdam Hornáček      * Parse OpenGrok Indexer options
436b5840353SAdam Hornáček      * This method was created so that it would be easier to write unit
437b5840353SAdam Hornáček      * tests against the Indexer option parsing mechanism.
438b5840353SAdam Hornáček      *
439779ff0e7SVladimir Kotal      * Limit usage lines to {@link org.opengrok.indexer.util.OptionParser.Option#MAX_DESCRIPTION_LINE_LENGTH}
440779ff0e7SVladimir Kotal      * characters for concise formatting.
441779ff0e7SVladimir Kotal      *
442b5840353SAdam Hornáček      * @param argv the command line arguments
443b5840353SAdam Hornáček      * @return array of remaining non option arguments
444b5840353SAdam Hornáček      * @throws ParseException if parsing failed
445b5840353SAdam Hornáček      */
parseOptions(String[] argv)446b5840353SAdam Hornáček     public static String[] parseOptions(String[] argv) throws ParseException {
44729816c3bSChris Fraire         final String[] usage = {HELP_OPT_1};
448b5840353SAdam Hornáček 
449b5840353SAdam Hornáček         if (argv.length == 0) {
450b5840353SAdam Hornáček             argv = usage;  // will force usage output
45162b82a8dSChris Fraire             status = 1; // with non-zero EXIT STATUS
452b5840353SAdam Hornáček         }
453b5840353SAdam Hornáček 
454a4876aa6SChris Fraire         /*
4557e5adef9SVladimir Kotal          * Pre-match any of the --help options so that some possible exception-generating args handlers (e.g. -R)
4567e5adef9SVladimir Kotal          * can be short-circuited.
457a4876aa6SChris Fraire          */
4582d875ddeSChris Fraire         boolean preHelp = Arrays.stream(argv).anyMatch(s -> HELP_OPT_1.equals(s) ||
459a4876aa6SChris Fraire                 HELP_OPT_2.equals(s) || HELP_OPT_3.equals(s));
460a4876aa6SChris Fraire 
461786b9f4bSChris Fraire         OptionParser configure = OptionParser.scan(parser ->
4628ae5e262SAdam Hornacek                 parser.on("-R configPath").execute(cfgFile -> {
463b5840353SAdam Hornáček             try {
464b5840353SAdam Hornáček                 cfg = Configuration.read(new File((String) cfgFile));
465b5840353SAdam Hornáček             } catch (IOException e) {
466982b7b22SChris Fraire                 if (!preHelp) {
467b5840353SAdam Hornáček                     die(e.getMessage());
468982b7b22SChris Fraire                 } else {
469c6f0939bSAdam Hornacek                     System.err.printf("Warning: failed to read -R %s%n", cfgFile);
470982b7b22SChris Fraire                 }
471b5840353SAdam Hornáček             }
472786b9f4bSChris Fraire         }));
473b5840353SAdam Hornáček 
474937bd40dSVladimir Kotal         searchPaths.clear();
475937bd40dSVladimir Kotal 
4768ae5e262SAdam Hornacek         optParser = OptionParser.execute(parser -> {
4777e5adef9SVladimir Kotal             parser.setPrologue(String.format("%nUsage: java -jar %s [options] [subDir1 [...]]%n", OPENGROK_JAR));
478b5840353SAdam Hornáček 
4795e426b1cSChris Fraire             parser.on(HELP_OPT_3, HELP_OPT_2, HELP_OPT_1, "=[mode]",
4805e426b1cSChris Fraire                     "With no mode specified, display this usage summary. Or specify a mode:",
4815e426b1cSChris Fraire                     "  config - display configuration.xml examples.",
4825e426b1cSChris Fraire                     "   ctags - display ctags command-line.",
483cfbc0ba5SChris Fraire                     "    guru - display AnalyzerGuru details.",
4848ae5e262SAdam Hornacek                     "   repos - display enabled repositories.").execute(v -> {
4855e426b1cSChris Fraire                         help = true;
486b5840353SAdam Hornáček                         helpUsage = parser.getUsage();
4875e426b1cSChris Fraire                         String mode = (String) v;
4885e426b1cSChris Fraire                         if (mode != null && !mode.isEmpty()) {
4895e426b1cSChris Fraire                             try {
4905e426b1cSChris Fraire                                 helpMode = HelpMode.valueOf(((String) v).toUpperCase(Locale.ROOT));
4915e426b1cSChris Fraire                             } catch (IllegalArgumentException ex) {
4925e426b1cSChris Fraire                                 die("mode '" + v + "' is not valid.");
4935e426b1cSChris Fraire                             }
4945e426b1cSChris Fraire                         }
495b5840353SAdam Hornáček             });
496b5840353SAdam Hornáček 
4971c258122SVladimir Kotal             parser.on("--apiTimeout", "=number", Integer.class,
4981c258122SVladimir Kotal                     "Set timeout for asynchronous API requests.").execute(v -> cfg.setApiTimeout((Integer) v));
4991c258122SVladimir Kotal 
5001c258122SVladimir Kotal             parser.on("--connectTimeout", "=number", Integer.class,
5011c258122SVladimir Kotal                     "Set connect timeout. Used for API requests.").execute(v -> cfg.setConnectTimeout((Integer) v));
5021c258122SVladimir Kotal 
503b5840353SAdam Hornáček             parser.on(
504f5124002SChris Fraire                 "-A (.ext|prefix.):(-|analyzer)", "--analyzer",
505f5124002SChris Fraire                     "/(\\.\\w+|\\w+\\.):(-|[a-zA-Z_0-9.]+)/",
506f5124002SChris Fraire                     "Associates files with the specified prefix or extension (case-",
507f5124002SChris Fraire                     "insensitive) to be analyzed with the given analyzer, where 'analyzer'",
508786b9f4bSChris Fraire                     "may be specified using a class name (case-sensitive e.g. RubyAnalyzer)",
509786b9f4bSChris Fraire                     "or analyzer language name (case-sensitive e.g. C). Option may be",
510786b9f4bSChris Fraire                     "repeated.",
511b5840353SAdam Hornáček                     "  Ex: -A .foo:CAnalyzer",
512b5840353SAdam Hornáček                     "      will use the C analyzer for all files ending with .FOO",
513b5840353SAdam Hornáček                     "  Ex: -A bar.:Perl",
514f5124002SChris Fraire                     "      will use the Perl analyzer for all files starting with",
515f5124002SChris Fraire                     "      \"BAR\" (no full-stop)",
516b5840353SAdam Hornáček                     "  Ex: -A .c:-",
517b5840353SAdam Hornáček                     "      will disable specialized analyzers for all files ending with .c").
5188ae5e262SAdam Hornacek                 execute(analyzerSpec -> {
519b5840353SAdam Hornáček                     String[] arg = ((String) analyzerSpec).split(":");
520b5840353SAdam Hornáček                     String fileSpec = arg[0];
521b5840353SAdam Hornáček                     String analyzer = arg[1];
522b5840353SAdam Hornáček                     configureFileAnalyzer(fileSpec, analyzer);
523b5840353SAdam Hornáček                 }
524b5840353SAdam Hornáček             );
525b5840353SAdam Hornáček 
526a483cb1cSChris Fraire             parser.on("-c", "--ctags", "=/path/to/ctags",
5278ae5e262SAdam Hornacek                     "Path to Universal Ctags. Default is ctags in environment PATH.").execute(
528786b9f4bSChris Fraire                             v -> cfg.setCtags((String) v));
529a483cb1cSChris Fraire 
530a483cb1cSChris Fraire             parser.on("--canonicalRoot", "=/path/",
531a483cb1cSChris Fraire                     "Allow symlinks to canonical targets starting with the specified root",
532a483cb1cSChris Fraire                     "without otherwise needing to specify -N,--symlink for such symlinks. A",
533a483cb1cSChris Fraire                     "canonical root must end with a file separator. For security, a canonical",
5348ae5e262SAdam Hornacek                     "root cannot be the root directory. Option may be repeated.").execute(v -> {
5350e0ac58dSChris Fraire                 String root = (String) v;
536fcbdc96bSChris Fraire                 String problem = CanonicalRootValidator.validate(root, "--canonicalRoot");
537fcbdc96bSChris Fraire                 if (problem != null) {
538fcbdc96bSChris Fraire                     die(problem);
5390e0ac58dSChris Fraire                 }
5400e0ac58dSChris Fraire                 canonicalRoots.add(root);
5410e0ac58dSChris Fraire             });
5420e0ac58dSChris Fraire 
54309f6ab57SVladimir Kotal             parser.on("--checkIndex", "Check index, exit with 0 on success,",
54409f6ab57SVladimir Kotal                     "with 1 on failure.").execute(v -> checkIndex = true);
545b5840353SAdam Hornáček 
546b5840353SAdam Hornáček             parser.on("-d", "--dataRoot", "=/path/to/data/root",
547b5840353SAdam Hornáček                 "The directory where OpenGrok stores the generated data.").
5488ae5e262SAdam Hornacek                 execute(drPath -> {
549b5840353SAdam Hornáček                     File dataRoot = new File((String) drPath);
550b5840353SAdam Hornáček                     if (!dataRoot.exists() && !dataRoot.mkdirs()) {
551b5840353SAdam Hornáček                         die("Cannot create data root: " + dataRoot);
552b5840353SAdam Hornáček                     }
553b5840353SAdam Hornáček                     if (!dataRoot.isDirectory()) {
554b5840353SAdam Hornáček                         die("Data root must be a directory");
555b5840353SAdam Hornáček                     }
556b5840353SAdam Hornáček                     try {
557b5840353SAdam Hornáček                         cfg.setDataRoot(dataRoot.getCanonicalPath());
558b5840353SAdam Hornáček                     } catch (IOException e) {
559b5840353SAdam Hornáček                         die(e.getMessage());
560b5840353SAdam Hornáček                     }
561b5840353SAdam Hornáček                 }
562b5840353SAdam Hornáček             );
563b5840353SAdam Hornáček 
564b5840353SAdam Hornáček             parser.on("--depth", "=number", Integer.class,
565b5840353SAdam Hornáček                 "Scanning depth for repositories in directory structure relative to",
5668ae5e262SAdam Hornacek                 "source root. Default is " + Configuration.defaultScanningDepth + ".").execute(depth ->
567786b9f4bSChris Fraire                     cfg.setScanningDepth((Integer) depth));
568b5840353SAdam Hornáček 
56929816c3bSChris Fraire             parser.on("--disableRepository", "=type_name",
570f5124002SChris Fraire                     "Disables operation of an OpenGrok-supported repository. See also",
571f5124002SChris Fraire                     "-h,--help repos. Option may be repeated.",
57229816c3bSChris Fraire                     "  Ex: --disableRepository git",
57329816c3bSChris Fraire                     "      will disable the GitRepository",
5748ae5e262SAdam Hornacek                     "  Ex: --disableRepository MercurialRepository").execute(v -> {
57529816c3bSChris Fraire                 String repoType = (String) v;
57629816c3bSChris Fraire                 String repoSimpleType = RepositoryFactory.matchRepositoryByName(repoType);
57729816c3bSChris Fraire                 if (repoSimpleType == null) {
578c6f0939bSAdam Hornacek                     System.err.printf("'--disableRepository %s' does not match a type and is ignored%n", v);
57929816c3bSChris Fraire                 } else {
58029816c3bSChris Fraire                     disabledRepositories.add(repoSimpleType);
58129816c3bSChris Fraire                 }
58229816c3bSChris Fraire             });
58329816c3bSChris Fraire 
584b5840353SAdam Hornáček             parser.on("-e", "--economical",
5855d6ffbcbSChris Fraire                     "To consume less disk space, OpenGrok will not generate and save",
5865d6ffbcbSChris Fraire                     "hypertext cross-reference files but will generate on demand, which could",
5878ae5e262SAdam Hornacek                     "be slightly slow.").execute(v -> cfg.setGenerateHtml(false));
588b5840353SAdam Hornáček 
589b5840353SAdam Hornáček             parser.on("-G", "--assignTags",
5908ae5e262SAdam Hornacek                 "Assign commit tags to all entries in history for all repositories.").execute(v ->
591786b9f4bSChris Fraire                     cfg.setTagsEnabled(true));
592b5840353SAdam Hornáček 
593202e6da1SVladimir Kotal             // for backward compatibility
594202e6da1SVladimir Kotal             parser.on("-H", "Enable history.").execute(v -> cfg.setHistoryEnabled(true));
595202e6da1SVladimir Kotal 
596202e6da1SVladimir Kotal             parser.on("--historyBased", "=on|off", ON_OFF, Boolean.class,
597202e6da1SVladimir Kotal                             "If history based reindex is in effect, the set of files ",
598202e6da1SVladimir Kotal                             "changed/deleted since the last reindex is determined from history ",
599202e6da1SVladimir Kotal                             "of the repositories. This needs history, history cache and ",
600202e6da1SVladimir Kotal                             "projects to be enabled. This should be much faster than the ",
601202e6da1SVladimir Kotal                             "classic way of traversing the directory structure. ",
602202e6da1SVladimir Kotal                             "The default is on. If you need to e.g. index files untracked by ",
603202e6da1SVladimir Kotal                             "SCM, set this to off. Currently works only for Git.",
604202e6da1SVladimir Kotal                             "All repositories in a project need to support this in order ",
605202e6da1SVladimir Kotal                             "to be indexed using history.").
606202e6da1SVladimir Kotal                     execute(v -> cfg.setHistoryBasedReindex((Boolean) v));
607b5840353SAdam Hornáček 
608ecb1ce82SVladimir Kotal             parser.on("--historyThreads", "=number", Integer.class,
609*607bcff5SVladimir Kotal                     "The number of threads to use for history cache generation on repository level. ",
610057b073fSVladimir Kotal                     "By default the number of threads will be set to the number of available CPUs.",
611057b073fSVladimir Kotal                     "Assumes -H/--history.").execute(threadCount ->
612ecb1ce82SVladimir Kotal                     cfg.setHistoryParallelism((Integer) threadCount));
613ecb1ce82SVladimir Kotal 
614dc607a84SVladimir Kotal             parser.on("--historyFileThreads", "=number", Integer.class,
615*607bcff5SVladimir Kotal                     "The number of threads to use for history cache generation ",
616*607bcff5SVladimir Kotal                     "when dealing with individual files.",
617c313ba23SVladimir Kotal                     "By default the number of threads will be set to the number of available CPUs.",
618c313ba23SVladimir Kotal                     "Assumes -H/--history.").execute(threadCount ->
619dc607a84SVladimir Kotal                     cfg.setHistoryFileParallelism((Integer) threadCount));
620ecb1ce82SVladimir Kotal 
621b5840353SAdam Hornáček             parser.on("-I", "--include", "=pattern",
6225d6ffbcbSChris Fraire                     "Only files matching this pattern will be examined. Pattern supports",
6238ae5e262SAdam Hornacek                     "wildcards (example: -I '*.java' -I '*.c'). Option may be repeated.").execute(
6245d6ffbcbSChris Fraire                             pattern -> cfg.getIncludedNames().add((String) pattern));
625b5840353SAdam Hornáček 
626b5840353SAdam Hornáček             parser.on("-i", "--ignore", "=pattern",
6275d6ffbcbSChris Fraire                     "Ignore matching files (prefixed with 'f:' or no prefix) or directories",
6285d6ffbcbSChris Fraire                     "(prefixed with 'd:'). Pattern supports wildcards (example: -i '*.so'",
6298ae5e262SAdam Hornacek                     "-i d:'test*'). Option may be repeated.").execute(pattern ->
6305d6ffbcbSChris Fraire                     cfg.getIgnoredNames().add((String) pattern));
631b5840353SAdam Hornáček 
632b5840353SAdam Hornáček             parser.on("-l", "--lock", "=on|off|simple|native", LUCENE_LOCKS,
633f5124002SChris Fraire                     "Set OpenGrok/Lucene locking mode of the Lucene database during index",
6348ae5e262SAdam Hornacek                     "generation. \"on\" is an alias for \"simple\". Default is off.").execute(v -> {
635b5840353SAdam Hornáček                 try {
636b5840353SAdam Hornáček                     if (v != null) {
637b5840353SAdam Hornáček                         String vuc = v.toString().toUpperCase(Locale.ROOT);
638b5840353SAdam Hornáček                         cfg.setLuceneLocking(LuceneLockName.valueOf(vuc));
639b5840353SAdam Hornáček                     }
640b5840353SAdam Hornáček                 } catch (IllegalArgumentException e) {
641c6f0939bSAdam Hornacek                     System.err.printf("`--lock %s' is invalid and ignored%n", v);
642b5840353SAdam Hornáček                 }
643b5840353SAdam Hornáček             });
644b5840353SAdam Hornáček 
645b5840353SAdam Hornáček             parser.on("--leadingWildCards", "=on|off", ON_OFF, Boolean.class,
6468ae5e262SAdam Hornacek                 "Allow or disallow leading wildcards in a search. Default is on.").execute(v ->
647786b9f4bSChris Fraire                     cfg.setAllowLeadingWildcard((Boolean) v));
648b5840353SAdam Hornáček 
649b5840353SAdam Hornáček             parser.on("-m", "--memory", "=number", Double.class,
6505d6ffbcbSChris Fraire                     "Amount of memory (MB) that may be used for buffering added documents and",
6515d6ffbcbSChris Fraire                     "deletions before they are flushed to the directory (default " +
6525d6ffbcbSChris Fraire                             Configuration.defaultRamBufferSize + ").",
6538ae5e262SAdam Hornacek                     "Please increase JVM heap accordingly too.").execute(memSize ->
6545d6ffbcbSChris Fraire                     cfg.setRamBufferSize((Double) memSize));
655b5840353SAdam Hornáček 
656ff44f24aSAdam Hornáček             parser.on("--mandoc", "=/path/to/mandoc", "Path to mandoc(1) binary.")
6578ae5e262SAdam Hornacek                     .execute(mandocPath -> cfg.setMandoc((String) mandocPath));
658b5840353SAdam Hornáček 
659875ab81aSChris Fraire             parser.on("-N", "--symlink", "=/path/to/symlink",
660a483cb1cSChris Fraire                     "Allow the symlink to be followed. Other symlinks targeting the same",
661a483cb1cSChris Fraire                     "canonical target or canonical children will be allowed too. Option may",
662f5124002SChris Fraire                     "be repeated. (By default only symlinks directly under the source root",
6638ae5e262SAdam Hornacek                     "directory are allowed. See also --canonicalRoot)").execute(v ->
6640e0ac58dSChris Fraire                     allowedSymlinks.add((String) v));
665875ab81aSChris Fraire 
666b5840353SAdam Hornáček             parser.on("-n", "--noIndex",
6675d6ffbcbSChris Fraire                     "Do not generate indexes and other data (such as history cache and xref",
6688ae5e262SAdam Hornacek                     "files), but process all other command line options.").execute(v ->
6690e0ac58dSChris Fraire                     runIndex = false);
670b5840353SAdam Hornáček 
671b256ec7eSVladimir Kotal             parser.on("--nestingMaximum", "=number", Integer.class,
6728df660a0SVladimir Kotal                     "Maximum depth of nested repositories. Default is 1.").execute(v ->
6732402fe1cSChris Fraire                     cfg.setNestingMaximum((Integer) v));
6742402fe1cSChris Fraire 
675b5840353SAdam Hornáček             parser.on("-O", "--optimize", "=on|off", ON_OFF, Boolean.class,
676f5124002SChris Fraire                     "Turn on/off the optimization of the index database as part of the",
677f5124002SChris Fraire                     "indexing step. Default is on.").
6788ae5e262SAdam Hornacek                 execute(v -> {
679b5840353SAdam Hornáček                     boolean oldval = cfg.isOptimizeDatabase();
680b5840353SAdam Hornáček                     cfg.setOptimizeDatabase((Boolean) v);
681b5840353SAdam Hornáček                     if (oldval != cfg.isOptimizeDatabase()) {
682b5840353SAdam Hornáček                         optimizedChanged = true;
683b5840353SAdam Hornáček                     }
684b5840353SAdam Hornáček                 }
685b5840353SAdam Hornáček             );
686b5840353SAdam Hornáček 
687b5840353SAdam Hornáček             parser.on("-o", "--ctagOpts", "=path",
688b5840353SAdam Hornáček                 "File with extra command line options for ctags.").
6898ae5e262SAdam Hornacek                 execute(path -> {
690b5840353SAdam Hornáček                     String CTagsExtraOptionsFile = (String) path;
691b5840353SAdam Hornáček                     File CTagsFile = new File(CTagsExtraOptionsFile);
692b5840353SAdam Hornáček                     if (!(CTagsFile.isFile() && CTagsFile.canRead())) {
693b5840353SAdam Hornáček                         die("File '" + CTagsExtraOptionsFile + "' not found for the -o option");
694b5840353SAdam Hornáček                     }
695b5840353SAdam Hornáček                     System.err.println("INFO: file with extra "
696b5840353SAdam Hornáček                         + "options for ctags: " + CTagsExtraOptionsFile);
697b5840353SAdam Hornáček                     cfg.setCTagsExtraOptionsFile(CTagsExtraOptionsFile);
698b5840353SAdam Hornáček                 }
699b5840353SAdam Hornáček             );
700b5840353SAdam Hornáček 
701b5840353SAdam Hornáček             parser.on("-P", "--projects",
7028ae5e262SAdam Hornacek                 "Generate a project for each top-level directory in source root.").execute(v -> {
703b5840353SAdam Hornáček                 addProjects = true;
704b5840353SAdam Hornáček                 cfg.setProjectsEnabled(true);
705b5840353SAdam Hornáček             });
706b5840353SAdam Hornáček 
7075d6ffbcbSChris Fraire             parser.on("-p", "--defaultProject", "=path/to/default/project",
7085d6ffbcbSChris Fraire                     "Path (relative to the source root) to a project that should be selected",
7095d6ffbcbSChris Fraire                     "by default in the web application (when no other project is set either",
7105d6ffbcbSChris Fraire                     "in a cookie or in parameter). Option may be repeated to specify several",
7118ae5e262SAdam Hornacek                     "projects. Use the special value __all__ to indicate all projects.").execute(v ->
7125d6ffbcbSChris Fraire                     defaultProjects.add((String) v));
713b5840353SAdam Hornáček 
714b5840353SAdam Hornáček             parser.on("--profiler", "Pause to await profiler or debugger.").
7158ae5e262SAdam Hornacek                 execute(v -> awaitProfiler = true);
716b5840353SAdam Hornáček 
717b5840353SAdam Hornáček             parser.on("--progress",
7188ae5e262SAdam Hornacek                     "Print per-project percentage progress information.").execute(v ->
7195d6ffbcbSChris Fraire                     cfg.setPrintProgress(true));
720b5840353SAdam Hornáček 
721b5840353SAdam Hornáček             parser.on("-Q", "--quickScan",  "=on|off", ON_OFF, Boolean.class,
7225d6ffbcbSChris Fraire                     "Turn on/off quick context scan. By default, only the first 1024KB of a",
7235d6ffbcbSChris Fraire                     "file is scanned, and a link ('[..all..]') is inserted when the file is",
7245d6ffbcbSChris Fraire                     "bigger. Activating this may slow the server down. (Note: this setting",
7258ae5e262SAdam Hornacek                     "only affects the web application.) Default is on.").execute(v ->
7265d6ffbcbSChris Fraire                     cfg.setQuickContextScan((Boolean) v));
727b5840353SAdam Hornáček 
728f5124002SChris Fraire             parser.on("-q", "--quiet",
7298ae5e262SAdam Hornacek                     "Run as quietly as possible. Sets logging level to WARNING.").execute(v ->
730786b9f4bSChris Fraire                     LoggerUtil.setBaseConsoleLogLevel(Level.WARNING));
731b5840353SAdam Hornáček 
732b5840353SAdam Hornáček             parser.on("-R /path/to/configuration",
7338ae5e262SAdam Hornacek                 "Read configuration from the specified file.").execute(v -> {
734b5840353SAdam Hornáček                 // Already handled above. This populates usage.
735b5840353SAdam Hornáček             });
736b5840353SAdam Hornáček 
737b5840353SAdam Hornáček             parser.on("-r", "--remote", "=on|off|uionly|dirbased",
738b5840353SAdam Hornáček                 REMOTE_REPO_CHOICES,
739b5840353SAdam Hornáček                 "Specify support for remote SCM systems.",
740b5840353SAdam Hornáček                 "      on - allow retrieval for remote SCM systems.",
741b5840353SAdam Hornáček                 "     off - ignore SCM for remote systems.",
742b5840353SAdam Hornáček                 "  uionly - support remote SCM for user interface only.",
743b5840353SAdam Hornáček                 "dirbased - allow retrieval during history index only for repositories",
744b5840353SAdam Hornáček                 "           which allow getting history for directories.").
7458ae5e262SAdam Hornacek                 execute(v -> {
746b5840353SAdam Hornáček                     String option = (String) v;
747b5840353SAdam Hornáček                     if (option.equalsIgnoreCase(ON)) {
748b5840353SAdam Hornáček                         cfg.setRemoteScmSupported(Configuration.RemoteSCM.ON);
749b5840353SAdam Hornáček                     } else if (option.equalsIgnoreCase(OFF)) {
750b5840353SAdam Hornáček                         cfg.setRemoteScmSupported(Configuration.RemoteSCM.OFF);
751b5840353SAdam Hornáček                     } else if (option.equalsIgnoreCase(DIRBASED)) {
752b5840353SAdam Hornáček                         cfg.setRemoteScmSupported(Configuration.RemoteSCM.DIRBASED);
753b5840353SAdam Hornáček                     } else if (option.equalsIgnoreCase(UIONLY)) {
754b5840353SAdam Hornáček                         cfg.setRemoteScmSupported(Configuration.RemoteSCM.UIONLY);
755b5840353SAdam Hornáček                     }
756b5840353SAdam Hornáček                 }
757b5840353SAdam Hornáček             );
758b5840353SAdam Hornáček 
759b5840353SAdam Hornáček             parser.on("--renamedHistory", "=on|off", ON_OFF, Boolean.class,
760b5840353SAdam Hornáček                 "Enable or disable generating history for renamed files.",
761b5840353SAdam Hornáček                 "If set to on, makes history indexing slower for repositories",
7628ae5e262SAdam Hornacek                 "with lots of renamed files. Default is off.").execute(v ->
763f5124002SChris Fraire                     cfg.setHandleHistoryOfRenamedFiles((Boolean) v));
764b5840353SAdam Hornáček 
76587a275e9SAnatoly Akkerman             parser.on("--repository", "=[path/to/repository|@file_with_paths]",
7667271f92fSChris Fraire                     "Path (relative to the source root) to a repository for generating",
7677271f92fSChris Fraire                     "history (if -H,--history is on). By default all discovered repositories",
7687271f92fSChris Fraire                     "are history-eligible; using --repository limits to only those specified.",
76987a275e9SAnatoly Akkerman                     "File containing paths can be specified via @path syntax.",
77087a275e9SAnatoly Akkerman                     "Option may be repeated.")
77187a275e9SAnatoly Akkerman                 .execute(v -> handlePathParameter(repositories, ((String) v).trim()));
7727271f92fSChris Fraire 
77387a275e9SAnatoly Akkerman             parser.on("-S", "--search", "=[path/to/repository|@file_with_paths]",
774c49b2babSChris Fraire                     "Search for source repositories under -s,--source, and add them. Path",
77587a275e9SAnatoly Akkerman                     "(relative to the source root) is optional. ",
77687a275e9SAnatoly Akkerman                     "File containing paths can be specified via @path syntax.",
77787a275e9SAnatoly Akkerman                     "Option may be repeated.")
77887a275e9SAnatoly Akkerman                 .execute(v -> {
779937bd40dSVladimir Kotal                         searchRepositories = true;
78087a275e9SAnatoly Akkerman                         String value = ((String) v).trim();
78187a275e9SAnatoly Akkerman                         if (!value.isEmpty()) {
78287a275e9SAnatoly Akkerman                             handlePathParameter(searchPaths, value);
783937bd40dSVladimir Kotal                         }
784937bd40dSVladimir Kotal                     });
785b5840353SAdam Hornáček 
786b5840353SAdam Hornáček             parser.on("-s", "--source", "=/path/to/source/root",
787b5840353SAdam Hornáček                 "The root directory of the source tree.").
7888ae5e262SAdam Hornacek                 execute(source -> {
789b5840353SAdam Hornáček                     File sourceRoot = new File((String) source);
790b5840353SAdam Hornáček                     if (!sourceRoot.isDirectory()) {
791b5840353SAdam Hornáček                         die("Source root " + sourceRoot + " must be a directory");
792b5840353SAdam Hornáček                     }
793b5840353SAdam Hornáček                     try {
794b5840353SAdam Hornáček                         cfg.setSourceRoot(sourceRoot.getCanonicalPath());
795b5840353SAdam Hornáček                     } catch (IOException e) {
796b5840353SAdam Hornáček                         die(e.getMessage());
797b5840353SAdam Hornáček                     }
798b5840353SAdam Hornáček                 }
799b5840353SAdam Hornáček             );
800b5840353SAdam Hornáček 
801b5840353SAdam Hornáček             parser.on("--style", "=path",
8025d6ffbcbSChris Fraire                     "Path to the subdirectory in the web application containing the requested",
8038ae5e262SAdam Hornacek                     "stylesheet. The factory-setting is: \"default\".").execute(stylePath ->
8045d6ffbcbSChris Fraire                     cfg.setWebappLAF((String) stylePath));
805b5840353SAdam Hornáček 
80660540ec3SVladimir Kotal             parser.on("-T", "--threads", "=number", Integer.class,
80760540ec3SVladimir Kotal                     "The number of threads to use for index generation, repository scan",
80860540ec3SVladimir Kotal                     "and repository invalidation.",
80960540ec3SVladimir Kotal                     "By default the number of threads will be set to the number of available",
81060540ec3SVladimir Kotal                     "CPUs. This influences the number of spawned ctags processes as well.").
81160540ec3SVladimir Kotal                     execute(threadCount -> cfg.setIndexingParallelism((Integer) threadCount));
81260540ec3SVladimir Kotal 
81360540ec3SVladimir Kotal             parser.on("-t", "--tabSize", "=number", Integer.class,
81460540ec3SVladimir Kotal                 "Default tab size to use (number of spaces per tab character).")
81560540ec3SVladimir Kotal                     .execute(tabSize -> cfg.setTabSize((Integer) tabSize));
81660540ec3SVladimir Kotal 
8178d34347cSVladimir Kotal             parser.on("--token", "=string|@file_with_string",
8188d34347cSVladimir Kotal                     "Authorization bearer API token to use when making API calls",
8198d34347cSVladimir Kotal                     "to the web application").
8208d34347cSVladimir Kotal                     execute(optarg -> {
8218d34347cSVladimir Kotal                         String value = ((String) optarg).trim();
8228d34347cSVladimir Kotal                         if (value.startsWith("@")) {
823195a23a5SVladimir Kotal                             try (BufferedReader in = new BufferedReader(new InputStreamReader(
824195a23a5SVladimir Kotal                                     new FileInputStream(Path.of(value).toString().substring(1))))) {
825195a23a5SVladimir Kotal                                 String token = in.readLine().trim();
8268d34347cSVladimir Kotal                                 cfg.setIndexerAuthenticationToken(token);
8278d34347cSVladimir Kotal                             } catch (IOException e) {
8288d34347cSVladimir Kotal                                 die("Failed to read from " + value);
8298d34347cSVladimir Kotal                             }
8308d34347cSVladimir Kotal                         } else {
8318d34347cSVladimir Kotal                             cfg.setIndexerAuthenticationToken(value);
8328d34347cSVladimir Kotal                         }
8338d34347cSVladimir Kotal                     });
8348d34347cSVladimir Kotal 
8355d6ffbcbSChris Fraire             parser.on("-U", "--uri", "=SCHEME://webappURI:port/contextPath",
8368ae5e262SAdam Hornacek                 "Send the current configuration to the specified web application.").execute(webAddr -> {
83755402125SVladimir Kotal                     webappURI = (String) webAddr;
838fa2acf12SVladimir Kotal                     try {
83955402125SVladimir Kotal                         URI uri = new URI(webappURI);
840fa2acf12SVladimir Kotal                         String scheme = uri.getScheme();
841fa2acf12SVladimir Kotal                         if (!scheme.equals("http") && !scheme.equals("https")) {
84255402125SVladimir Kotal                             die("webappURI '" + webappURI + "' does not have HTTP/HTTPS scheme");
843fa2acf12SVladimir Kotal                         }
844fa2acf12SVladimir Kotal                     } catch (URISyntaxException e) {
84555402125SVladimir Kotal                         die("URL '" + webappURI + "' is not valid.");
84653d11a05SVladimir Kotal                     }
84753d11a05SVladimir Kotal 
84853d11a05SVladimir Kotal                     env = RuntimeEnvironment.getInstance();
84955402125SVladimir Kotal                     env.setConfigURI(webappURI);
850b5840353SAdam Hornáček                 }
851b5840353SAdam Hornáček             );
852b5840353SAdam Hornáček 
853b5840353SAdam Hornáček             parser.on("---unitTest");  // For unit test only, will not appear in help
854b5840353SAdam Hornáček 
855b5840353SAdam Hornáček             parser.on("--updateConfig",
8568ae5e262SAdam Hornacek                     "Populate the web application with a bare configuration, and exit.").execute(v ->
8575d6ffbcbSChris Fraire                     bareConfig = true);
858b5840353SAdam Hornáček 
859b5840353SAdam Hornáček             parser.on("--userPage", "=URL",
860b5840353SAdam Hornáček                 "Base URL of the user Information provider.",
8617e5adef9SVladimir Kotal                 "Example: \"https://www.example.org/viewProfile.jspa?username=\".",
8628ae5e262SAdam Hornacek                 "Use \"none\" to disable link.").execute(v -> cfg.setUserPage((String) v));
863b5840353SAdam Hornáček 
864b5840353SAdam Hornáček             parser.on("--userPageSuffix", "=URL-suffix",
865ff44f24aSAdam Hornáček                 "URL Suffix for the user Information provider. Default: \"\".")
8668ae5e262SAdam Hornacek                     .execute(suffix -> cfg.setUserPageSuffix((String) suffix));
867b5840353SAdam Hornáček 
8688ae5e262SAdam Hornacek             parser.on("-V", "--version", "Print version, and quit.").execute(v -> {
869b5840353SAdam Hornáček                 System.out.println(Info.getFullVersion());
870b5840353SAdam Hornáček                 System.exit(0);
871b5840353SAdam Hornáček             });
872b5840353SAdam Hornáček 
8738ae5e262SAdam Hornacek             parser.on("-v", "--verbose", "Set logging level to INFO.").execute(v -> {
87483adc92cSVladimir Kotal                 verbose = true;
875b5840353SAdam Hornáček                 LoggerUtil.setBaseConsoleLogLevel(Level.INFO);
876b5840353SAdam Hornáček             });
877b5840353SAdam Hornáček 
878b5840353SAdam Hornáček             parser.on("-W", "--writeConfig", "=/path/to/configuration",
879f5124002SChris Fraire                     "Write the current configuration to the specified file (so that the web",
8808ae5e262SAdam Hornacek                     "application can use the same configuration).").execute(configFile ->
881f5124002SChris Fraire                     configFilename = (String) configFile);
8827271f92fSChris Fraire 
8837271f92fSChris Fraire             parser.on("--webappCtags", "=on|off", ON_OFF, Boolean.class,
8847271f92fSChris Fraire                     "Web application should run ctags when necessary. Default is off.").
8858ae5e262SAdam Hornacek                     execute(v -> cfg.setWebappCtags((Boolean) v));
886b5840353SAdam Hornáček         });
887b5840353SAdam Hornáček 
888202e6da1SVladimir Kotal         // Need to read the configuration file first, so that options may be overwritten later.
889b5840353SAdam Hornáček         configure.parse(argv);
890b5840353SAdam Hornáček 
89111cc45afSVladimir Kotal         LOGGER.log(Level.INFO, "Indexer options: {0}", Arrays.toString(argv));
89211cc45afSVladimir Kotal 
893b5840353SAdam Hornáček         if (cfg == null) {
894b5840353SAdam Hornáček             cfg = new Configuration();
895b5840353SAdam Hornáček         }
896b5840353SAdam Hornáček 
897b5840353SAdam Hornáček         cfg.setHistoryEnabled(false);  // force user to turn on history capture
898b5840353SAdam Hornáček 
89946414f58SVladimir Kotal         argv = optParser.parse(argv);
900b5840353SAdam Hornáček 
901b5840353SAdam Hornáček         return argv;
902b5840353SAdam Hornáček     }
903b5840353SAdam Hornáček 
die(String message)904b5840353SAdam Hornáček     private static void die(String message) {
905b5840353SAdam Hornáček         System.err.println("ERROR: " + message);
906b5840353SAdam Hornáček         System.exit(1);
907b5840353SAdam Hornáček     }
908b5840353SAdam Hornáček 
configureFileAnalyzer(String fileSpec, String analyzer)909b5840353SAdam Hornáček     private static void configureFileAnalyzer(String fileSpec, String analyzer) {
910b5840353SAdam Hornáček 
911b5840353SAdam Hornáček         boolean prefix = false;
912b5840353SAdam Hornáček 
913b5840353SAdam Hornáček         // removing '.' from file specification
914b5840353SAdam Hornáček         // expecting either ".extensionName" or "prefixName."
915b5840353SAdam Hornáček         if (fileSpec.endsWith(".")) {
916b5840353SAdam Hornáček             fileSpec = fileSpec.substring(0, fileSpec.lastIndexOf('.'));
917b5840353SAdam Hornáček             prefix = true;
918b5840353SAdam Hornáček         } else {
919b5840353SAdam Hornáček             fileSpec = fileSpec.substring(1);
920b5840353SAdam Hornáček         }
92152dccac1SChris Fraire         fileSpec = fileSpec.toUpperCase(Locale.ROOT);
922b5840353SAdam Hornáček 
923b5840353SAdam Hornáček         // Disable analyzer?
924b5840353SAdam Hornáček         if (analyzer.equals("-")) {
925b5840353SAdam Hornáček             if (prefix) {
926b5840353SAdam Hornáček                 AnalyzerGuru.addPrefix(fileSpec, null);
927b5840353SAdam Hornáček             } else {
928b5840353SAdam Hornáček                 AnalyzerGuru.addExtension(fileSpec, null);
929b5840353SAdam Hornáček             }
930b5840353SAdam Hornáček         } else {
931b5840353SAdam Hornáček             try {
932b5840353SAdam Hornáček                 if (prefix) {
933b5840353SAdam Hornáček                     AnalyzerGuru.addPrefix(
934b5840353SAdam Hornáček                         fileSpec,
935b5840353SAdam Hornáček                         AnalyzerGuru.findFactory(analyzer));
936b5840353SAdam Hornáček                 } else {
937b5840353SAdam Hornáček                     AnalyzerGuru.addExtension(
938b5840353SAdam Hornáček                         fileSpec,
939b5840353SAdam Hornáček                         AnalyzerGuru.findFactory(analyzer));
940b5840353SAdam Hornáček                 }
941b5840353SAdam Hornáček 
942b5840353SAdam Hornáček             } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | NoSuchMethodException
943b5840353SAdam Hornáček                     | InvocationTargetException e) {
944b5840353SAdam Hornáček                 LOGGER.log(Level.SEVERE, "Unable to locate FileAnalyzerFactory for {0}", analyzer);
945b5840353SAdam Hornáček                 LOGGER.log(Level.SEVERE, "Stack: ", e.fillInStackTrace());
946b5840353SAdam Hornáček                 System.exit(1);
947b5840353SAdam Hornáček             }
948b5840353SAdam Hornáček         }
949b5840353SAdam Hornáček     }
950b5840353SAdam Hornáček 
951b5840353SAdam Hornáček     /**
952ff44f24aSAdam Hornáček      * Write configuration to a file.
953b5840353SAdam Hornáček      * @param env runtime environment
954b5840353SAdam Hornáček      * @param filename file name to write the configuration to
955b5840353SAdam Hornáček      * @throws IOException if I/O exception occurred
956b5840353SAdam Hornáček      */
writeConfigToFile(RuntimeEnvironment env, String filename)957b5840353SAdam Hornáček     public static void writeConfigToFile(RuntimeEnvironment env, String filename) throws IOException {
958b5840353SAdam Hornáček         if (filename != null) {
959b5840353SAdam Hornáček             LOGGER.log(Level.INFO, "Writing configuration to {0}", filename);
960b5840353SAdam Hornáček             env.writeConfiguration(new File(filename));
9611ac92230SAdam Hornáček             LOGGER.log(Level.INFO, "Done writing configuration to {0}", filename);
962b5840353SAdam Hornáček         }
963b5840353SAdam Hornáček     }
964b5840353SAdam Hornáček 
9658ea3083fSVladimir Kotal     // Wrapper for prepareIndexer() that always generates history cache.
prepareIndexer(RuntimeEnvironment env, boolean searchRepositories, boolean addProjects, boolean createDict, List<String> subFiles, List<String> repositories)9668ea3083fSVladimir Kotal     public void prepareIndexer(RuntimeEnvironment env,
9678ea3083fSVladimir Kotal                                boolean searchRepositories,
9688ea3083fSVladimir Kotal                                boolean addProjects,
9698ea3083fSVladimir Kotal                                boolean createDict,
9708ea3083fSVladimir Kotal                                List<String> subFiles,
97118d0c95fSVladimir Kotal                                List<String> repositories) throws IndexerException, IOException {
9727516a8e8SVladimir Kotal 
973937bd40dSVladimir Kotal         prepareIndexer(env,
974937bd40dSVladimir Kotal                 searchRepositories ? Collections.singleton(env.getSourceRootPath()) : Collections.emptySet(),
975937bd40dSVladimir Kotal                 addProjects, createDict, true, subFiles, repositories);
9768ea3083fSVladimir Kotal     }
9778ea3083fSVladimir Kotal 
9788ea3083fSVladimir Kotal     /**
9798ea3083fSVladimir Kotal      * Generate history cache and/or scan the repositories.
9808ea3083fSVladimir Kotal      *
981b5840353SAdam Hornáček      * This is the first phase of the indexing where history cache is being
982b5840353SAdam Hornáček      * generated for repositories (at least for those which support getting
983b5840353SAdam Hornáček      * history per directory).
984b5840353SAdam Hornáček      *
9858ea3083fSVladimir Kotal      * @param env runtime environment
986937bd40dSVladimir Kotal      * @param searchPaths list of paths in which to search for repositories
9878ea3083fSVladimir Kotal      * @param addProjects if true, add projects
9888ea3083fSVladimir Kotal      * @param createDict if true, create dictionary
98981b586e6SVladimir Kotal      * @param createHistoryCache create history cache flag
9908ea3083fSVladimir Kotal      * @param subFiles list of directories
9918ea3083fSVladimir Kotal      * @param repositories list of repositories
99281b586e6SVladimir Kotal      * @throws IndexerException indexer exception
99381b586e6SVladimir Kotal      * @throws IOException I/O exception
994b5840353SAdam Hornáček      */
prepareIndexer(RuntimeEnvironment env, Set<String> searchPaths, boolean addProjects, boolean createDict, boolean createHistoryCache, List<String> subFiles, List<String> repositories)995b5840353SAdam Hornáček     public void prepareIndexer(RuntimeEnvironment env,
996937bd40dSVladimir Kotal             Set<String> searchPaths,
997b5840353SAdam Hornáček             boolean addProjects,
998b5840353SAdam Hornáček             boolean createDict,
9998ea3083fSVladimir Kotal             boolean createHistoryCache,
1000b5840353SAdam Hornáček             List<String> subFiles,
100118d0c95fSVladimir Kotal             List<String> repositories) throws IndexerException, IOException {
1002b5840353SAdam Hornáček 
100309dc2337SVladimir Kotal         if (!env.validateUniversalCtags()) {
1004a9a037c4SVladimir Kotal             throw new IndexerException("Didn't find Universal Ctags");
1005b5840353SAdam Hornáček         }
1006b5840353SAdam Hornáček 
1007b5840353SAdam Hornáček         // Projects need to be created first since when adding repositories below,
10087e5adef9SVladimir Kotal         // some project properties might be needed for that.
1009b5840353SAdam Hornáček         if (addProjects) {
1010d1e826faSAdam Hornáček             File[] files = env.getSourceRootFile().listFiles();
1011b5840353SAdam Hornáček             Map<String, Project> projects = env.getProjects();
1012b5840353SAdam Hornáček 
10137e5adef9SVladimir Kotal             addProjects(files, projects);
10147e5adef9SVladimir Kotal         }
10157e5adef9SVladimir Kotal 
10167e5adef9SVladimir Kotal         if (!searchPaths.isEmpty()) {
1017741f2ca7SVladimir Kotal             LOGGER.log(Level.INFO, "Scanning for repositories in {0} (down to {1} levels below source root)",
1018741f2ca7SVladimir Kotal                     new Object[]{searchPaths, env.getScanningDepth()});
10197e5adef9SVladimir Kotal             Statistics stats = new Statistics();
10207e5adef9SVladimir Kotal             env.setRepositories(searchPaths.toArray(new String[0]));
10217e5adef9SVladimir Kotal             stats.report(LOGGER, String.format("Done scanning for repositories, found %d repositories",
10227e5adef9SVladimir Kotal                     env.getRepositories().size()), "indexer.repository.scan");
10237e5adef9SVladimir Kotal         }
10247e5adef9SVladimir Kotal 
10257e5adef9SVladimir Kotal         if (createHistoryCache) {
10267e5adef9SVladimir Kotal             // Even if history is disabled globally, it can be enabled for some repositories.
10277e5adef9SVladimir Kotal             if (repositories != null && !repositories.isEmpty()) {
10287e5adef9SVladimir Kotal                 LOGGER.log(Level.INFO, "Generating history cache for repositories: {0}",
10297e5adef9SVladimir Kotal                         String.join(",", repositories));
1030779ff0e7SVladimir Kotal                 HistoryGuru.getInstance().
1031779ff0e7SVladimir Kotal                         createCache(repositories);
10327e5adef9SVladimir Kotal             } else {
10337e5adef9SVladimir Kotal                 LOGGER.log(Level.INFO, "Generating history cache for all repositories ...");
10347e5adef9SVladimir Kotal                 HistoryGuru.getInstance().createCache();
10357e5adef9SVladimir Kotal             }
10367e5adef9SVladimir Kotal             LOGGER.info("Done generating history cache");
10377e5adef9SVladimir Kotal         }
10387e5adef9SVladimir Kotal 
10397e5adef9SVladimir Kotal         if (createDict) {
10407e5adef9SVladimir Kotal             IndexDatabase.listFrequentTokens(subFiles);
10417e5adef9SVladimir Kotal         }
10427e5adef9SVladimir Kotal     }
10437e5adef9SVladimir Kotal 
addProjects(File[] files, Map<String, Project> projects)10447e5adef9SVladimir Kotal     private void addProjects(File[] files, Map<String, Project> projects) {
1045b5840353SAdam Hornáček         // Keep a copy of the old project list so that we can preserve
1046b5840353SAdam Hornáček         // the customization of existing projects.
1047b5840353SAdam Hornáček         Map<String, Project> oldProjects = new HashMap<>();
1048b5840353SAdam Hornáček         for (Project p : projects.values()) {
1049b5840353SAdam Hornáček             oldProjects.put(p.getName(), p);
1050b5840353SAdam Hornáček         }
1051b5840353SAdam Hornáček 
1052b5840353SAdam Hornáček         projects.clear();
1053b5840353SAdam Hornáček 
1054b5840353SAdam Hornáček         // Add a project for each top-level directory in source root.
1055b5840353SAdam Hornáček         for (File file : files) {
1056b5840353SAdam Hornáček             String name = file.getName();
1057bd3709daSVladimir Kotal             String path = '/' + name;
1058b5840353SAdam Hornáček             if (oldProjects.containsKey(name)) {
1059b5840353SAdam Hornáček                 // This is an existing object. Reuse the old project,
1060b5840353SAdam Hornáček                 // possibly with customizations, instead of creating a
1061b5840353SAdam Hornáček                 // new with default values.
1062b5840353SAdam Hornáček                 Project p = oldProjects.get(name);
1063b5840353SAdam Hornáček                 p.setPath(path);
1064b5840353SAdam Hornáček                 p.setName(name);
10652ffbb0cfSVladimir Kotal                 p.completeWithDefaults();
1066b5840353SAdam Hornáček                 projects.put(name, p);
1067b5840353SAdam Hornáček             } else if (!name.startsWith(".") && file.isDirectory()) {
1068b5840353SAdam Hornáček                 // Found a new directory with no matching project, so
1069b5840353SAdam Hornáček                 // create a new project with default properties.
10702ffbb0cfSVladimir Kotal                 projects.put(name, new Project(name, path));
1071b5840353SAdam Hornáček             }
1072b5840353SAdam Hornáček         }
1073b5840353SAdam Hornáček     }
1074b5840353SAdam Hornáček 
1075b5840353SAdam Hornáček     /**
1076b5840353SAdam Hornáček      * This is the second phase of the indexer which generates Lucene index
1077b5840353SAdam Hornáček      * by passing source code files through ctags, generating xrefs
1078b5840353SAdam Hornáček      * and storing data from the source files in the index (along with history,
1079b5840353SAdam Hornáček      * if any).
1080b5840353SAdam Hornáček      *
1081b5840353SAdam Hornáček      * @param update if set to true, index database is updated, otherwise optimized
1082b5840353SAdam Hornáček      * @param subFiles index just some subdirectories
1083b5840353SAdam Hornáček      * @param progress object to receive notifications as indexer progress is made
1084b5840353SAdam Hornáček      * @throws IOException if I/O exception occurred
1085b5840353SAdam Hornáček      */
doIndexerExecution(final boolean update, List<String> subFiles, IndexChangedListener progress)1086b5840353SAdam Hornáček     public void doIndexerExecution(final boolean update, List<String> subFiles,
1087b5840353SAdam Hornáček         IndexChangedListener progress)
1088b5840353SAdam Hornáček             throws IOException {
1089b5840353SAdam Hornáček         Statistics elapsed = new Statistics();
1090b5840353SAdam Hornáček         LOGGER.info("Starting indexing");
1091b5840353SAdam Hornáček 
10927e5adef9SVladimir Kotal         RuntimeEnvironment env = RuntimeEnvironment.getInstance();
1093e829566cSChris Fraire         IndexerParallelizer parallelizer = env.getIndexerParallelizer();
1094e829566cSChris Fraire         final CountDownLatch latch;
1095b5840353SAdam Hornáček         if (subFiles == null || subFiles.isEmpty()) {
1096b5840353SAdam Hornáček             if (update) {
1097e829566cSChris Fraire                 latch = IndexDatabase.updateAll(progress);
1098b5840353SAdam Hornáček             } else if (env.isOptimizeDatabase()) {
1099e829566cSChris Fraire                 latch = IndexDatabase.optimizeAll();
1100e829566cSChris Fraire             } else {
1101e829566cSChris Fraire                 latch = new CountDownLatch(0);
1102b5840353SAdam Hornáček             }
1103b5840353SAdam Hornáček         } else {
1104b5840353SAdam Hornáček             List<IndexDatabase> dbs = new ArrayList<>();
1105b5840353SAdam Hornáček 
1106b5840353SAdam Hornáček             for (String path : subFiles) {
1107b5840353SAdam Hornáček                 Project project = Project.getProject(path);
1108b5840353SAdam Hornáček                 if (project == null && env.hasProjects()) {
1109b5840353SAdam Hornáček                     LOGGER.log(Level.WARNING, "Could not find a project for \"{0}\"", path);
1110b5840353SAdam Hornáček                 } else {
1111b5840353SAdam Hornáček                     IndexDatabase db;
1112b5840353SAdam Hornáček                     if (project == null) {
1113b5840353SAdam Hornáček                         db = new IndexDatabase();
1114b5840353SAdam Hornáček                     } else {
1115b5840353SAdam Hornáček                         db = new IndexDatabase(project);
1116b5840353SAdam Hornáček                     }
1117b5840353SAdam Hornáček                     int idx = dbs.indexOf(db);
1118b5840353SAdam Hornáček                     if (idx != -1) {
1119b5840353SAdam Hornáček                         db = dbs.get(idx);
1120b5840353SAdam Hornáček                     }
1121b5840353SAdam Hornáček 
1122b5840353SAdam Hornáček                     if (db.addDirectory(path)) {
1123b5840353SAdam Hornáček                         if (idx == -1) {
1124b5840353SAdam Hornáček                             dbs.add(db);
1125b5840353SAdam Hornáček                         }
1126b5840353SAdam Hornáček                     } else {
1127b5840353SAdam Hornáček                         LOGGER.log(Level.WARNING, "Directory does not exist \"{0}\"", path);
1128b5840353SAdam Hornáček                     }
1129b5840353SAdam Hornáček                 }
1130b5840353SAdam Hornáček             }
1131b5840353SAdam Hornáček 
1132e829566cSChris Fraire             latch = new CountDownLatch(dbs.size());
1133b5840353SAdam Hornáček             for (final IndexDatabase db : dbs) {
1134b5840353SAdam Hornáček                 final boolean optimize = env.isOptimizeDatabase();
1135b5840353SAdam Hornáček                 db.addIndexChangedListener(progress);
1136c6f0939bSAdam Hornacek                 parallelizer.getFixedExecutor().submit(() -> {
1137b5840353SAdam Hornáček                     try {
1138b5840353SAdam Hornáček                         if (update) {
1139e829566cSChris Fraire                             db.update();
1140b5840353SAdam Hornáček                         } else if (optimize) {
1141b5840353SAdam Hornáček                             db.optimize();
1142b5840353SAdam Hornáček                         }
1143b5840353SAdam Hornáček                     } catch (Throwable e) {
1144b5840353SAdam Hornáček                         LOGGER.log(Level.SEVERE, "An error occurred while "
1145b5840353SAdam Hornáček                                 + (update ? "updating" : "optimizing")
1146b5840353SAdam Hornáček                                 + " index", e);
1147e829566cSChris Fraire                     } finally {
1148e829566cSChris Fraire                         latch.countDown();
1149b5840353SAdam Hornáček                     }
1150b5840353SAdam Hornáček                 });
1151b5840353SAdam Hornáček             }
1152b5840353SAdam Hornáček         }
1153b5840353SAdam Hornáček 
1154e829566cSChris Fraire         // Wait forever for the executors to finish.
1155b5840353SAdam Hornáček         try {
1156cb82f663SKryštof Tulinger             LOGGER.info("Waiting for the executors to finish");
1157007bf260SVladimir Kotal             latch.await();
1158b5840353SAdam Hornáček         } catch (InterruptedException exp) {
1159e829566cSChris Fraire             LOGGER.log(Level.WARNING, "Received interrupt while waiting" +
1160e829566cSChris Fraire                     " for executor to finish", exp);
1161b5840353SAdam Hornáček         }
11620d7ace53SVladimir Kotal         elapsed.report(LOGGER, "Done indexing data of all repositories", "indexer.repository.indexing");
1163823830a9SVladimir Kotal 
1164823830a9SVladimir Kotal         CtagsUtil.deleteTempFiles();
1165b5840353SAdam Hornáček     }
1166b5840353SAdam Hornáček 
sendToConfigHost(RuntimeEnvironment env, String host)11674ce4e2b9SAdam Hornáček     public void sendToConfigHost(RuntimeEnvironment env, String host) {
11684ce4e2b9SAdam Hornáček         LOGGER.log(Level.INFO, "Sending configuration to: {0}", host);
1169b5840353SAdam Hornáček         try {
11704ce4e2b9SAdam Hornáček             env.writeConfiguration(host);
11711c258122SVladimir Kotal         } catch (IOException | IllegalArgumentException ex) {
1172b5840353SAdam Hornáček             LOGGER.log(Level.SEVERE, String.format(
11734ce4e2b9SAdam Hornáček                     "Failed to send configuration to %s "
11744ce4e2b9SAdam Hornáček                     + "(is web application server running with opengrok deployed?)", host), ex);
11751c258122SVladimir Kotal         } catch (InterruptedException e) {
11761c258122SVladimir Kotal             LOGGER.log(Level.WARNING, "interrupted while sending configuration");
1177b5840353SAdam Hornáček         }
1178b5840353SAdam Hornáček         LOGGER.info("Configuration update routine done, check log output for errors.");
1179b5840353SAdam Hornáček     }
1180b5840353SAdam Hornáček 
pauseToAwaitProfiler()1181b5840353SAdam Hornáček     private static void pauseToAwaitProfiler() {
1182b5840353SAdam Hornáček         Scanner scan = new Scanner(System.in);
1183b5840353SAdam Hornáček         String in;
1184b5840353SAdam Hornáček         do {
1185b5840353SAdam Hornáček             System.out.print("Start profiler. Continue (Y/N)? ");
1186b5840353SAdam Hornáček             in = scan.nextLine().toLowerCase(Locale.ROOT);
1187b5840353SAdam Hornáček         } while (!in.equals("y") && !in.equals("n"));
1188b5840353SAdam Hornáček 
1189a72324b1SAdam Hornáček         if (in.equals("n")) {
1190a72324b1SAdam Hornáček             System.exit(1);
1191a72324b1SAdam Hornáček         }
1192b5840353SAdam Hornáček     }
1193b5840353SAdam Hornáček 
119487a275e9SAnatoly Akkerman     // Visible for testing
handlePathParameter(Collection<String> paramValueStore, String pathValue)119587a275e9SAnatoly Akkerman     static void handlePathParameter(Collection<String> paramValueStore, String pathValue) {
119687a275e9SAnatoly Akkerman         if (pathValue.startsWith("@")) {
119787a275e9SAnatoly Akkerman             paramValueStore.addAll(loadPathsFromFile(pathValue.substring(1)));
119887a275e9SAnatoly Akkerman         } else {
119987a275e9SAnatoly Akkerman             paramValueStore.add(pathValue);
120087a275e9SAnatoly Akkerman         }
120187a275e9SAnatoly Akkerman     }
120287a275e9SAnatoly Akkerman 
loadPathsFromFile(String filename)120387a275e9SAnatoly Akkerman     private static List<String> loadPathsFromFile(String filename) {
120487a275e9SAnatoly Akkerman         try {
120587a275e9SAnatoly Akkerman             return Files.readAllLines(Path.of(filename));
120687a275e9SAnatoly Akkerman         } catch (IOException e) {
120787a275e9SAnatoly Akkerman             LOGGER.log(Level.SEVERE, String.format("Could not load paths from %s", filename), e);
120887a275e9SAnatoly Akkerman             throw new UncheckedIOException(e);
120987a275e9SAnatoly Akkerman         }
121087a275e9SAnatoly Akkerman     }
121187a275e9SAnatoly Akkerman 
exitWithHelp()1212f5124002SChris Fraire     private static void exitWithHelp() {
1213f5124002SChris Fraire         PrintStream helpStream = status != 0 ? System.err : System.out;
1214f5124002SChris Fraire         switch (helpMode) {
1215f5124002SChris Fraire             case CONFIG:
1216f5124002SChris Fraire                 helpStream.print(ConfigurationHelp.getSamples());
1217f5124002SChris Fraire                 break;
1218f5124002SChris Fraire             case CTAGS:
1219982b7b22SChris Fraire                 /*
1220982b7b22SChris Fraire                  * Force the environment's ctags, because this method is called
1221982b7b22SChris Fraire                  * before main() does the heavyweight setConfiguration().
1222982b7b22SChris Fraire                  */
1223982b7b22SChris Fraire                 env.setCtags(cfg.getCtags());
1224f5124002SChris Fraire                 helpStream.println("Ctags command-line:");
1225f5124002SChris Fraire                 helpStream.println();
1226f5124002SChris Fraire                 helpStream.println(getCtagsCommand());
1227f5124002SChris Fraire                 helpStream.println();
1228f5124002SChris Fraire                 break;
1229f5124002SChris Fraire             case GURU:
1230f5124002SChris Fraire                 helpStream.println(AnalyzerGuruHelp.getUsage());
1231f5124002SChris Fraire                 break;
1232f5124002SChris Fraire             case REPOS:
1233982b7b22SChris Fraire                 /*
1234982b7b22SChris Fraire                  * Force the environment's disabledRepositories (as above).
1235982b7b22SChris Fraire                  */
1236982b7b22SChris Fraire                 env.setDisabledRepositories(cfg.getDisabledRepositories());
1237f5124002SChris Fraire                 helpStream.println(RepositoriesHelp.getText());
1238f5124002SChris Fraire                 break;
1239f5124002SChris Fraire             default:
1240f5124002SChris Fraire                 helpStream.println(helpUsage);
1241f5124002SChris Fraire                 break;
1242f5124002SChris Fraire         }
1243f5124002SChris Fraire         System.exit(status);
1244f5124002SChris Fraire     }
1245f5124002SChris Fraire 
getCtagsCommand()12462d875ddeSChris Fraire     private static String getCtagsCommand() {
12472d875ddeSChris Fraire         Ctags ctags = CtagsUtil.newInstance(env);
12486c62ede9SAdam Hornacek         return Executor.escapeForShell(ctags.getArgv(), true, SystemUtils.IS_OS_WINDOWS);
12492d875ddeSChris Fraire     }
12502d875ddeSChris Fraire 
12515e426b1cSChris Fraire     private enum HelpMode {
1252cfbc0ba5SChris Fraire         CONFIG, CTAGS, DEFAULT, GURU, REPOS
12535e426b1cSChris Fraire     }
12545e426b1cSChris Fraire 
Indexer()1255b5840353SAdam Hornáček     private Indexer() {
1256b5840353SAdam Hornáček     }
1257b5840353SAdam Hornáček }
1258