diff --git a/tycho-apitools-plugin/src/main/java/org/eclipse/tycho/apitools/ApiAnalysisMojo.java b/tycho-apitools-plugin/src/main/java/org/eclipse/tycho/apitools/ApiAnalysisMojo.java index a38bc47726..692f2c5685 100644 --- a/tycho-apitools-plugin/src/main/java/org/eclipse/tycho/apitools/ApiAnalysisMojo.java +++ b/tycho-apitools-plugin/src/main/java/org/eclipse/tycho/apitools/ApiAnalysisMojo.java @@ -148,7 +148,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { if (eclipseProjectValue.isEmpty() || !eclipseProjectValue.get().hasNature(ApiPlugin.NATURE_ID)) { return; } - + EclipseProject eclipseProject = eclipseProjectValue.get(); if (supportedPackagingTypes.contains(project.getPackaging())) { Log log = getLog(); if (skipIfReplaced && wasReplaced()) { @@ -179,14 +179,15 @@ public void execute() throws MojoExecutionException, MojoFailureException { } ApiAnalysisResult analysisResult; if (parallel) { - analysisResult = performAnalysis(baselineBundles, dependencyBundles, eclipseFramework); + analysisResult = performAnalysis(baselineBundles, dependencyBundles, eclipseFramework, eclipseProject); } else { synchronized (ApiAnalysisMojo.class) { // due to // https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/issues/3885#note_1266412 we // can not execute more than one analysis without excessive memory consumption // unless this is fixed it is safer to only run one analysis at a time - analysisResult = performAnalysis(baselineBundles, dependencyBundles, eclipseFramework); + analysisResult = performAnalysis(baselineBundles, dependencyBundles, eclipseFramework, + eclipseProject); } } log.info("API Analysis finished in " + time(start) + "."); @@ -253,10 +254,11 @@ public void execute() throws MojoExecutionException, MojoFailureException { } private ApiAnalysisResult performAnalysis(Collection baselineBundles, Collection dependencyBundles, - EclipseFramework eclipseFramework) throws MojoExecutionException { + EclipseFramework eclipseFramework, EclipseProject eclipseProject) throws MojoExecutionException { try { ApiAnalysis analysis = new ApiAnalysis(baselineBundles, dependencyBundles, project.getName(), - fileToPath(apiFilter), fileToPath(apiPreferences), fileToPath(project.getBasedir()), debug, + eclipseProject.getFile(fileToPath(apiFilter)), eclipseProject.getFile(fileToPath(apiPreferences)), + fileToPath(project.getBasedir()), debug, fileToPath(project.getArtifact().getFile()), stringToPath(project.getBuild().getOutputDirectory())); return eclipseFramework.execute(analysis); diff --git a/tycho-compiler-plugin/src/main/java/org/eclipse/tycho/compiler/AbstractOsgiCompilerMojo.java b/tycho-compiler-plugin/src/main/java/org/eclipse/tycho/compiler/AbstractOsgiCompilerMojo.java index 97d8db75e2..ab42d0e5eb 100644 --- a/tycho-compiler-plugin/src/main/java/org/eclipse/tycho/compiler/AbstractOsgiCompilerMojo.java +++ b/tycho-compiler-plugin/src/main/java/org/eclipse/tycho/compiler/AbstractOsgiCompilerMojo.java @@ -725,18 +725,22 @@ protected CompilerConfiguration getCompilerConfiguration(List compileSou CompilerConfiguration compilerConfiguration = super.getCompilerConfiguration(compileSourceRoots, compileSourceExcludes); if (useProjectSettings) { - String prefsFilePath = project.getBasedir() + File.separator + PREFS_FILE_PATH; - if (!new File(prefsFilePath).exists()) { - getLog().debug("Parameter 'useProjectSettings' is set to true, but preferences file '" + prefsFilePath - + "' could not be found"); - } else { + Path prefsFilePath = tychoProjectManager.getEclipseProject(project) + .map(eclipse -> eclipse.getFile(PREFS_FILE_PATH)) + .orElseGet(() -> new File(project.getBasedir(), PREFS_FILE_PATH).toPath()); + + if (Files.isRegularFile(prefsFilePath)) { // make sure that "-properties" is the first custom argument, otherwise it's not possible to override // any project setting on the command line because the last argument wins. List> copy = new ArrayList<>( compilerConfiguration.getCustomCompilerArgumentsEntries()); compilerConfiguration.getCustomCompilerArgumentsEntries().clear(); - addCompilerCustomArgument(compilerConfiguration, "-properties", prefsFilePath); + addCompilerCustomArgument(compilerConfiguration, "-properties", + prefsFilePath.toAbsolutePath().toString()); compilerConfiguration.getCustomCompilerArgumentsEntries().addAll(copy); + } else { + getLog().debug("Parameter 'useProjectSettings' is set to true, but preferences file '" + prefsFilePath + + "' could not be found"); } } compilerConfiguration.setTargetVersion(getTargetLevel()); diff --git a/tycho-metadata-model/src/main/java/org/eclipse/tycho/model/project/EclipseProject.java b/tycho-metadata-model/src/main/java/org/eclipse/tycho/model/project/EclipseProject.java index a97eca6575..d5dcf08220 100644 --- a/tycho-metadata-model/src/main/java/org/eclipse/tycho/model/project/EclipseProject.java +++ b/tycho-metadata-model/src/main/java/org/eclipse/tycho/model/project/EclipseProject.java @@ -14,6 +14,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.Collection; /** * represents information gathered from an "eclipse project" usually stored in a file named @@ -33,4 +34,22 @@ public interface EclipseProject { static EclipseProject parse(Path projectFile) throws IOException { return ProjectParser.parse(projectFile); } + + /** + * Resolves a path according to the project, this will resolve linked resources + * + * @param path + * @return the resolved path + */ + Path getFile(Path path); + + /** + * Resolves a path according to the project, this will resolve linked resources + * + * @param path + * @return the resolved path + */ + Path getFile(String path); + + Collection getVariables(); } diff --git a/tycho-metadata-model/src/main/java/org/eclipse/tycho/model/project/ProjectParser.java b/tycho-metadata-model/src/main/java/org/eclipse/tycho/model/project/ProjectParser.java index 0709ff93db..b75f053d35 100644 --- a/tycho-metadata-model/src/main/java/org/eclipse/tycho/model/project/ProjectParser.java +++ b/tycho-metadata-model/src/main/java/org/eclipse/tycho/model/project/ProjectParser.java @@ -14,10 +14,21 @@ import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collection; import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -31,6 +42,8 @@ class ProjectParser { + private static final Pattern PARENT_PROJECT_PATTERN = Pattern.compile("PARENT-(\\d+)-PROJECT_LOC"); + public static EclipseProject parse(Path path) throws IOException { try (InputStream stream = Files.newInputStream(path)) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @@ -58,6 +71,15 @@ public static EclipseProject parse(Path path) throws IOException { for (int i = 0; i < length; i++) { natureSet.add(natureNodes.item(i).getTextContent()); } + NodeList variableNodes = root.getElementsByTagName("variable"); + List variables = IntStream.range(0, variableNodes.getLength()) + .mapToObj(i -> variableNodes.item(i)).map(Element.class::cast).map(e -> parseVariable(e)) + .filter(Objects::nonNull).toList(); + NodeList linkNodes = root.getElementsByTagName("link"); + Map links = IntStream.range(0, linkNodes.getLength()) + .mapToObj(i -> linkNodes.item(i)).map(Element.class::cast).map(e -> parseLink(e)) + .filter(Objects::nonNull).collect(Collectors.toMap(LinkDescription::name, Function.identity())); + return new EclipseProject() { @Override @@ -100,10 +122,128 @@ public String getComment() { } return comment; } + + @Override + public Path getFile(Path path) { + if (path == null) { + return null; + } + if (!path.isAbsolute()) { + path = location.resolve(path); + } + if (Files.isRegularFile(path)) { + //if the file is already there we don't need to bother any links + return path; + } + Path relative = location.relativize(path); + for (Entry entry : links.entrySet()) { + Path linkPath = entry.getKey(); + if (relative.startsWith(linkPath)) { + LinkDescription link = entry.getValue(); + if (link.type() == LinkDescription.FILE) { + //the path must actually match each others as it is a file! + if (linkPath.startsWith(relative)) { + Path resolvedPath = resolvePath(link.locationURI()); + return location.resolve(resolvedPath).normalize(); + } + } else if (link.type() == LinkDescription.FOLDER) { + Path linkRelative = linkPath.relativize(relative); + Path resolvedPath = resolvePath(link.locationURI()); + if (resolvedPath != null) { + return location.resolve(resolvedPath).resolve(linkRelative).normalize(); + } + } + } + } + return path; + } + + @Override + public Path getFile(String path) { + return getFile(location.resolve(path)); + } + + @Override + public Collection getVariables() { + return variables; + } }; } catch (SAXException | ParserConfigurationException e) { throw new IOException("parsing failed", e); } } + private static Path resolvePath(URI uri) { + String schemeSpecificPart = uri.getSchemeSpecificPart(); + if (schemeSpecificPart != null) { + Path path = Path.of(schemeSpecificPart); + int count = path.getNameCount(); + if (count > 0) { + //only the first path is allowed to be a variable... + Path first = path.getName(0); + String name = first.toString(); + Matcher parentMatcher = PARENT_PROJECT_PATTERN.matcher(name); + if (parentMatcher.matches()) { + Path resolvedPath = Path.of(".."); + int p = Integer.parseInt(parentMatcher.group(1)); + for (int i = 1; i < p; i++) { + resolvedPath = resolvedPath.resolve(".."); + } + for (int i = 1; i < count; i++) { + resolvedPath = resolvedPath.resolve(path.getName(i)); + } + return resolvedPath; + } + return path; + } + } + return null; + } + + private static ProjectVariable parseVariable(Element element) { + try { + String name = element.getElementsByTagName("name").item(0).getTextContent(); + String value = element.getElementsByTagName("value").item(0).getTextContent(); + return new ProjectVariable(name, value); + } catch (RuntimeException e) { + //something is wrong here... + return null; + } + } + + private static LinkDescription parseLink(Element element) { + try { + String name = element.getElementsByTagName("name").item(0).getTextContent(); + String type = element.getElementsByTagName("type").item(0).getTextContent(); + String locationURI = element.getElementsByTagName("locationURI").item(0).getTextContent(); + return new LinkDescription(Path.of(name), Integer.parseInt(type), URI.create(locationURI)); + } catch (RuntimeException e) { + //something is wrong here... + return null; + } + } + + static final record LinkDescription(Path name, int type, URI locationURI) { + /** + * Type constant (bit mask value 1) which identifies file resources. + */ + static final int FILE = 0x1; + + /** + * Type constant (bit mask value 2) which identifies folder resources. + */ + static final int FOLDER = 0x2; + + /** + * Type constant (bit mask value 4) which identifies project resources. + */ + static final int PROJECT = 0x4; + + /** + * Type constant (bit mask value 8) which identifies the root resource. + */ + static final int ROOT = 0x8; + + } + } diff --git a/tycho-metadata-model/src/main/java/org/eclipse/tycho/model/project/ProjectVariable.java b/tycho-metadata-model/src/main/java/org/eclipse/tycho/model/project/ProjectVariable.java new file mode 100644 index 0000000000..2fc37c31b2 --- /dev/null +++ b/tycho-metadata-model/src/main/java/org/eclipse/tycho/model/project/ProjectVariable.java @@ -0,0 +1,5 @@ +package org.eclipse.tycho.model.project; + +public record ProjectVariable(String name, String value) { + +}