blob: e41375327304a99b5d46d6e4fed5bdcdab9ccc49 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.scenario.builder;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Multi-version matcher
*/
public class VersionMatcher {
private static final Logger logger = LoggerFactory.getLogger(VersionMatcher.class);
public static final String CASE_VERSIONS_FILE = "caseVersionsFile";
public static final String CANDIDATE_VERSIONS = "candidateVersions";
public static final String OUTPUT_FILE = "outputFile";
public static final String VERSIONS_LIMIT = "versionsLimit";
public static final String INCLUDE_CASE_SPECIFIC_VERSION = "includeCaseSpecificVersion";
public static void main(String[] args) throws Exception {
String caseVersionsFile = System.getProperty(CASE_VERSIONS_FILE);
String candidateVersionListStr = System.getProperty(CANDIDATE_VERSIONS);
String outputFile = System.getProperty(OUTPUT_FILE);
int limit = Integer.parseInt(System.getProperty(VERSIONS_LIMIT, "4"));
// whether include specific version which defined in case-versions.conf
// specific version: a real version not contains wildcard '*'
boolean includeCaseSpecificVersion = Boolean.parseBoolean(System.getProperty(INCLUDE_CASE_SPECIFIC_VERSION, "true"));
if (StringUtils.isBlank(candidateVersionListStr)) {
logger.error("Missing system prop: '{}'", CANDIDATE_VERSIONS);
System.exit(1);
}
if (StringUtils.isBlank(caseVersionsFile)) {
logger.error("Missing system prop: '{}'", CASE_VERSIONS_FILE);
System.exit(1);
}
File file = new File(caseVersionsFile);
if (!file.exists() || !file.isFile()) {
logger.error("file not exists or isn't a file: {}", file.getAbsolutePath());
System.exit(1);
}
if (StringUtils.isBlank(outputFile)) {
logger.error("Missing system prop: '{}'", OUTPUT_FILE);
System.exit(1);
}
new File(outputFile).getParentFile().mkdirs();
logger.info("{}: {}", CANDIDATE_VERSIONS, candidateVersionListStr);
logger.info("{}: {}", CASE_VERSIONS_FILE, caseVersionsFile);
logger.info("{}: {}", OUTPUT_FILE, outputFile);
// parse and expand to versions list
List<String> candidateVersionList = parseVersionList(candidateVersionListStr);
// parse case version rules
Map<String, List<String>> caseVersionRules = parseCaseVersionRules(caseVersionsFile);
Map<String, List<String>> matchVersions = new LinkedHashMap<>();
caseVersionRules.forEach((component, versionPatterns) -> {
for (String versionPattern : versionPatterns) {
// convert 'component:version_prefix*' to regex
String regex = "\\Q" + component + ":" + versionPattern.replace("*", "\\E.*?\\Q") + "\\E";
Pattern pattern = Pattern.compile(regex);
boolean matched = false;
for (String version : candidateVersionList) {
if (pattern.matcher(version).matches()) {
getMatchVersionList(matchVersions, component).add(version);
matched = true;
}
}
//add case specific version
if (!matched && includeCaseSpecificVersion && !versionPattern.contains("*")) {
getMatchVersionList(matchVersions, component).add(component+":"+versionPattern);
}
}
});
if (caseVersionRules.size() != matchVersions.size()) {
List<String> components = new ArrayList<>(caseVersionRules.keySet());
components.removeAll(matchVersions.keySet());
for (String component : components) {
logger.error("Component not match, component: {}, rules: {}, candidateVersionList: {}", component, caseVersionRules.get(component), candidateVersionListStr);
}
System.exit(2);
}
List<List<String>> versionProfiles = new ArrayList<>();
for (Map.Entry<String, List<String>> entry : matchVersions.entrySet()) {
String component = entry.getKey();
List<String> versions = entry.getValue();
versionProfiles = appendComponent(versionProfiles, component, versions);
}
if (versionProfiles.isEmpty()) {
logger.error("");
System.exit(2);
}
if (versionProfiles.size() > limit) {
logger.warn("Version matrix size exceeds limit and will be truncated, total: {}, limit: {}", versionProfiles.size(), limit);
}
try (FileOutputStream fos = new FileOutputStream(outputFile);
PrintWriter pw = new PrintWriter(fos)) {
StringBuilder sb = new StringBuilder();
int size = Math.min(versionProfiles.size(), limit);
for (int i = 0; i < size; i++) {
List<String> profile = versionProfiles.get(i);
for (String s : profile) {
sb.append("-D").append(s).append(" ");
}
sb.append("\n");
}
pw.print(sb);
logger.info("Version matrix: \n{}", sb);
} catch (IOException e) {
logger.error("Write version matrix failed: " + e.toString(), e);
}
}
private static List<String> getMatchVersionList(Map<String, List<String>> matchVersions, String component) {
return matchVersions.computeIfAbsent(component, (key) -> new ArrayList<>());
}
private static List<List<String>> appendComponent(List<List<String>> versionProfiles, String component, List<String> versions) {
List<List<String>> newProfiles = new ArrayList<>();
for (String version : versions) {
if (versionProfiles.isEmpty()) {
List<String> newProfile = new ArrayList<>();
newProfile.add(version);
newProfiles.add(newProfile);
} else {
//extends version matrix
for (int i = 0; i < versionProfiles.size(); i++) {
List<String> profile = versionProfiles.get(i);
List<String> newProfile = new ArrayList<>(profile);
newProfile.add(version);
newProfiles.add(newProfile);
}
}
}
return newProfiles;
}
private static Map<String, List<String>> parseCaseVersionRules(String caseVersionsFile) throws Exception {
//dubbo.version=2.7*, 3.*
//spring.version=4.*, 5.*
try {
Map<String, List<String>> ruleMap = new LinkedHashMap<>();
String content = FileUtil.readFully(caseVersionsFile);
BufferedReader br = new BufferedReader(new StringReader(content));
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.startsWith("#") || StringUtils.isBlank(line)) {
continue;
}
String[] strs = line.split("=");
String component = strs[0].trim();
String patternStr = strs[1];
String[] patterns = patternStr.split(",");
List<String> patternList = new ArrayList<>();
for (String pattern : patterns) {
patternList.add(pattern.trim());
}
ruleMap.put(component, patternList);
}
return ruleMap;
} catch (Exception e) {
logger.error("Parse case versions rules failed: {}", caseVersionsFile, e);
throw e;
}
}
private static List<String> parseVersionList(String versionListStr) {
// "<component1>:<version1>[,version2];<component2>:<version1>[,version2]"
// "<component1>:<version1>[;component1:<version2];<component2>:<version1>[;component2:version2];"
List<String> versionList = new ArrayList<>();
//split components by ';' or '\n'
String[] compvers = versionListStr.split("[;\n]");
for (String compver : compvers) {
if (StringUtils.isBlank(compver)) {
continue;
}
String[] strs = compver.split(":");
String component = strs[0].trim();
String[] vers = strs[1].split(",");
for (String ver : vers) {
versionList.add(component + ":" + ver.trim());
}
}
return versionList;
}
}