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