diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml index 07a77430..89d71dda 100644 --- a/.github/workflows/ant.yml +++ b/.github/workflows/ant.yml @@ -101,9 +101,11 @@ jobs: - eclipse-202006-jdk8 - eclipse-202212 - eclipse-202309 + - eclipse-202312 - eclipse-oxygen-full - eclipse-2022-03-full - eclipse-2023-09-full + - eclipse-2023-12-full - ecj11 - ecj14 - ecj16 diff --git a/buildScripts/ivy.xml b/buildScripts/ivy.xml index b165300b..6a5cd0eb 100644 --- a/buildScripts/ivy.xml +++ b/buildScripts/ivy.xml @@ -32,6 +32,7 @@ + @@ -64,6 +65,7 @@ + @@ -132,6 +134,19 @@ + + + + + + + + + + + + + diff --git a/buildScripts/setup.ant.xml b/buildScripts/setup.ant.xml index f6329a3a..f8fb5e51 100644 --- a/buildScripts/setup.ant.xml +++ b/buildScripts/setup.ant.xml @@ -45,25 +45,21 @@ This buildfile is part of projectlombok.org. It sets up the build itself. - - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - Full eclipse testing requires downloading a native SWT binding. This script knows how to download for OS = [mac, linux, or windows] and architecture = [aarch64 or x86-64]. You have something different, you unique snowflake you. Your OS: "${os.name}", Your arch: "${os.arch}". + Full eclipse testing requires downloading a native SWT binding. This script knows how to download for OS = [mac, linux, or windows] and architecture = [aarch64 or x86-64]. You have something different, you unique snowflake you. Your OS: "${os.name}", Your arch: "${os.arch}". @@ -160,15 +156,23 @@ This buildfile is part of projectlombok.org. It sets up the build itself. - + - + - + + + + + + + + + @@ -192,13 +196,31 @@ This buildfile is part of projectlombok.org. It sets up the build itself. - - - - + + + + + + + + + + + + + + + + + + + + + + @@ -206,133 +228,9 @@ This buildfile is part of projectlombok.org. It sets up the build itself. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/buildScripts/tests.ant.xml b/buildScripts/tests.ant.xml index c7f7a865..ade6fbff 100644 --- a/buildScripts/tests.ant.xml +++ b/buildScripts/tests.ant.xml @@ -218,6 +218,36 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -261,6 +291,14 @@ This buildfile is part of projectlombok.org. It takes care of compiling and runn + + + + + + + + diff --git a/src/support/lombok/eclipse/dependencies/Child.java b/src/support/lombok/eclipse/dependencies/Child.java new file mode 100644 index 00000000..06b93793 --- /dev/null +++ b/src/support/lombok/eclipse/dependencies/Child.java @@ -0,0 +1,8 @@ +package lombok.eclipse.dependencies; + +import javax.xml.bind.annotation.XmlAttribute; + +public class Child { + @XmlAttribute + String location; +} \ No newline at end of file diff --git a/src/support/lombok/eclipse/dependencies/DownloadEclipseDependencies.java b/src/support/lombok/eclipse/dependencies/DownloadEclipseDependencies.java index 2ff6839e..2391a1a9 100644 --- a/src/support/lombok/eclipse/dependencies/DownloadEclipseDependencies.java +++ b/src/support/lombok/eclipse/dependencies/DownloadEclipseDependencies.java @@ -7,71 +7,57 @@ import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Arrays; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.List; +import java.util.Set; /** * Download eclipse bundles. */ public class DownloadEclipseDependencies { - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws Exception { String target = args[0]; String eclipseVersion = args[1]; - String updatePage = args[2]; - String[] packages = Arrays.copyOfRange(args, 3, args.length); + String updateSiteUrl = args[2]; + boolean resolveDependencies = Boolean.parseBoolean(args[3]); + List bundles = Arrays.asList(Arrays.copyOfRange(args, 4, args.length)); + + UpdateSite updateSite = new UpdateSite(); + updateSite.read(updateSiteUrl); + + final Set artifacts; + if (resolveDependencies) { + artifacts = updateSite.resolveWithDependencies(bundles); + } else { + artifacts = updateSite.resolveWithoutDependencies(bundles); + } String pluginTarget = target + "/" + eclipseVersion + "/plugins/"; + String pluginSource = updateSite.getResolvedUrl() + "/plugins/"; - String indexData = readUrlAsString(updatePage); - - for (String pkg : packages) { - Matcher matcher = Pattern.compile("(" + pkg.replace(".", "\\.") + "_.*?\\.jar)").matcher(indexData); - if (matcher.find()) { - String path = matcher.group(1); - - try { - downloadFile(path, updatePage, pluginTarget); - } catch (Exception e) { - } - - int index = path.lastIndexOf("_"); - String source = path.substring(0, index) + ".source" + path.substring(index); - if (indexData.contains(source)) { - try { - downloadFile(source, updatePage, pluginTarget); - } catch (Exception e) { - } - } - } else { - System.out.println("Bundle \"" + pkg + "\" not found"); + for (String artifact : artifacts) { + try { + downloadFile(artifact, pluginSource, pluginTarget); + } catch (Exception e) { + } + + int index = artifact.lastIndexOf("_"); + String source = artifact.substring(0, index) + ".source" + artifact.substring(index); + try { + downloadFile(source, pluginSource, pluginTarget); + } catch (Exception e) { } } writeEclipseLibrary(target, eclipseVersion); } - private static String readUrlAsString(String url) throws MalformedURLException, IOException { - InputStream in = getStreamForUrl(url); - - StringBuilder sb = new StringBuilder(); - - int bufferSize = 1024; - char[] buffer = new char[bufferSize]; - InputStreamReader reader = new InputStreamReader(in, "UTF-8"); - for (int count = 0; (count = reader.read(buffer, 0, bufferSize)) > 0;) { - sb.append(buffer, 0, count); - } - return sb.toString(); - } - private static void downloadFile(String filename, String repositoryUrl, String target) throws IOException { new File(target).mkdirs(); File targetFile = new File(target, filename); @@ -87,10 +73,13 @@ public class DownloadEclipseDependencies { out = new FileOutputStream(targetFile); copy(in, out); System.out.println("[done]"); - } catch(IOException e) { + } catch (IOException e) { System.out.println("[error]"); } finally { - if (in != null) try { in.close(); } catch (Exception ignore) {} + if (in != null) try { + in.close(); + } catch (Exception ignore) { + } if (out != null) out.close(); } } @@ -111,7 +100,7 @@ public class DownloadEclipseDependencies { InputStream in = new BufferedInputStream(connection.getInputStream()); return in; } - + private static void writeEclipseLibrary(String target, String eclipseVersion) throws IOException { StringBuilder sb = new StringBuilder(); sb.append("\n"); @@ -121,7 +110,8 @@ public class DownloadEclipseDependencies { sb.append("\" systemlibrary=\"false\">\n"); File[] files = new File(new File(target, eclipseVersion), "plugins").listFiles(new FilenameFilter() { - @Override public boolean accept(File dir, String name) { + @Override + public boolean accept(File dir, String name) { return name.endsWith(".jar") && !name.contains(".source_"); } }); diff --git a/src/support/lombok/eclipse/dependencies/Provided.java b/src/support/lombok/eclipse/dependencies/Provided.java new file mode 100644 index 00000000..6ca7d6f3 --- /dev/null +++ b/src/support/lombok/eclipse/dependencies/Provided.java @@ -0,0 +1,12 @@ +package lombok.eclipse.dependencies; + +import javax.xml.bind.annotation.XmlAttribute; + +public class Provided { + @XmlAttribute + String namespace; + @XmlAttribute + String name; + @XmlAttribute + String version; +} diff --git a/src/support/lombok/eclipse/dependencies/Repository.java b/src/support/lombok/eclipse/dependencies/Repository.java new file mode 100644 index 00000000..8423d7ee --- /dev/null +++ b/src/support/lombok/eclipse/dependencies/Repository.java @@ -0,0 +1,18 @@ +package lombok.eclipse.dependencies; + +import java.util.List; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +@XmlRootElement +public class Repository { + @XmlElementWrapper(name = "children") + @XmlElement(name="child") + List children; + + @XmlElementWrapper(name = "units") + @XmlElement(name="unit") + List units; +} \ No newline at end of file diff --git a/src/support/lombok/eclipse/dependencies/Required.java b/src/support/lombok/eclipse/dependencies/Required.java new file mode 100644 index 00000000..5f1f895d --- /dev/null +++ b/src/support/lombok/eclipse/dependencies/Required.java @@ -0,0 +1,17 @@ +package lombok.eclipse.dependencies; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; + +public class Required { + @XmlAttribute + String namespace; + @XmlAttribute + String name; + @XmlAttribute + String range; + @XmlAttribute + boolean optional; + @XmlElement + String filter; +} diff --git a/src/support/lombok/eclipse/dependencies/UniqueQueue.java b/src/support/lombok/eclipse/dependencies/UniqueQueue.java new file mode 100644 index 00000000..7ec4a38c --- /dev/null +++ b/src/support/lombok/eclipse/dependencies/UniqueQueue.java @@ -0,0 +1,21 @@ +package lombok.eclipse.dependencies; + +import java.util.ArrayDeque; +import java.util.HashSet; +import java.util.Set; + +/** + * A incomplete implementation of a queue that maintains a history of all queued items and ensures unique elements. + * Elements cannot be added to the queue if they already exist in the history. + */ +public class UniqueQueue extends ArrayDeque { + private Set added = new HashSet<>(); + + @Override + public boolean add(T e) { + if (added.add(e)) { + return super.add(e); + } + return false; + } +} diff --git a/src/support/lombok/eclipse/dependencies/Unit.java b/src/support/lombok/eclipse/dependencies/Unit.java new file mode 100644 index 00000000..51827cf0 --- /dev/null +++ b/src/support/lombok/eclipse/dependencies/Unit.java @@ -0,0 +1,27 @@ +package lombok.eclipse.dependencies; + +import java.util.List; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; + +public class Unit { + @XmlAttribute + String id; + @XmlAttribute + String version; + + @XmlElementWrapper(name = "provides") + @XmlElement(name="provided") + List provides; + + @XmlElementWrapper(name = "requires") + @XmlElement(name="required") + List requires; + + @Override + public String toString() { + return id + "_" + version; + } +} diff --git a/src/support/lombok/eclipse/dependencies/UpdateSite.java b/src/support/lombok/eclipse/dependencies/UpdateSite.java new file mode 100644 index 00000000..93da4120 --- /dev/null +++ b/src/support/lombok/eclipse/dependencies/UpdateSite.java @@ -0,0 +1,194 @@ +package lombok.eclipse.dependencies; + +import java.io.BufferedInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.Source; +import javax.xml.transform.sax.SAXSource; + +import org.xml.sax.InputSource; + +public class UpdateSite { + private static final String OS_NAME = System.getProperty("os.name").toLowerCase(); + private static final String OSGI_OS = OS_NAME.contains("windows") ? "windows" : OS_NAME.contains("mac") ? "mac" : "linux"; + private static final String OS_ARCH = System.getProperty("os.arch"); + private static final String OSGI_ARCH = OS_ARCH.equals("aarch64") ? "aarch64" : "x86_64"; + + private JAXBContext jaxbContext; + private Repository repository; + private Map> providesIndex; + private String resolvedUrl; + private SAXParserFactory saxParserFactory; + + public UpdateSite() throws Exception { + jaxbContext = JAXBContext.newInstance(Repository.class); + providesIndex = new HashMap<>(); + + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + spf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + spf.setXIncludeAware(false); + saxParserFactory = spf; + } + + public void read(String url) throws Exception { + String currentUrl = url; + try { + while (true) { + String child = resolveNextChild(currentUrl); + if (child.startsWith("https://")) { + currentUrl = child; + } else { + currentUrl += child; + } + } + } catch (FileNotFoundException e) { + // Found the real repository + } + + resolvedUrl = currentUrl; + + try (InputStream inputStream = readJarOrXml(resolvedUrl, "content")) { + repository = unmarshalRepository(inputStream); + + // Build index + for (Unit unit : repository.units) { + for (Provided provides : unit.provides) { + providesIndex.computeIfAbsent(provides.namespace + ":" + provides.name, k -> new ArrayList<>()).add(unit); + } + } + } + ; + } + + public Set resolveWithoutDependencies(List dependencies) { + return resolve(dependencies, false); + } + + public Set resolveWithDependencies(List dependencies) { + return resolve(dependencies, true); + } + + private Set resolve(List dependencies, boolean withDependencies) { + Queue toResolve = new UniqueQueue<>(); + for (String dependency : dependencies) { + toResolve.add(dependency); + } + Set resolved = new HashSet<>(); + while (!toResolve.isEmpty()) { + String next = toResolve.poll(); + + List providedUnits = providesIndex.get(next); + // Skip unknown + if (providedUnits == null) { + System.out.println("Skipping unknown unit " + next); + continue; + } + // Remove a.jre.javase dependency + List filteredProvidedUnits = providedUnits.stream() + .filter(u -> !u.id.equals("a.jre.javase")) // Remove + .collect(Collectors.toList()); + + if (filteredProvidedUnits.size() == 0) { + // This is a JDK only dependency, skip + continue; + } + + // Skip ambiguous (we could use version ranges to solve that...) + if (filteredProvidedUnits.size() > 1) { + boolean alreadyResolved = filteredProvidedUnits.stream().anyMatch(resolved::contains); + if (!alreadyResolved) { + System.out.println("Ambiguous resolution for " + next + ": " + filteredProvidedUnits.toString()); + continue; + } + } + + Unit unit = filteredProvidedUnits.get(0); + resolved.add(unit); + + if (withDependencies && unit.requires != null) { + for (Required required : unit.requires) { + if (required.optional) continue; + if (!matchesFilter(required.filter)) continue; + + toResolve.add(required.namespace + ":" + required.name); + } + } + } + + return resolved.stream().map(u -> u.toString() + ".jar").collect(Collectors.toSet()); + } + + // Dummy implementation + private boolean matchesFilter(String filter) { + if (filter == null) { + return true; + } + if (filter.contains("osgi.arch=") && !filter.contains("osgi.arch=" + OSGI_ARCH)) { + return false; + } + if (filter.contains("osgi.os=") && !filter.contains("osgi.os=" + OSGI_OS)) { + return false; + } + return true; + } + + private String resolveNextChild(String currentUrl) throws Exception { + try (InputStream inputStream = readJarOrXml(currentUrl, "compositeContent")) { + Repository repository = unmarshalRepository(inputStream); + Child lastChild = repository.children.get(repository.children.size() - 1); + return lastChild.location; + } + } + + private Repository unmarshalRepository(InputStream inputStream) throws Exception { + Source source = new SAXSource(saxParserFactory.newSAXParser().getXMLReader(), new InputSource(inputStream)); + Repository repository = (Repository) jaxbContext.createUnmarshaller().unmarshal(source); + return repository; + } + + private InputStream readJarOrXml(String url, String name) throws Exception { + try { + return getStreamForUrl(url + "/" + name + ".xml"); + } catch (FileNotFoundException e) { + System.out.println("Not found, trying jar"); + } + ZipInputStream zipInputStream = new ZipInputStream(getStreamForUrl(url + "/" + name + ".jar")); + ZipEntry entry; + while ((entry = zipInputStream.getNextEntry()) != null) { + if (entry.getName().equals(name + ".xml")) { + return zipInputStream; + } + } + throw new FileNotFoundException(); + } + + private static InputStream getStreamForUrl(String url) throws Exception { + System.out.println("Reading " + url); + HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); + connection.setRequestProperty("User-Agent", "lombok"); + connection.setRequestProperty("Accept", "*/*"); + InputStream in = new BufferedInputStream(connection.getInputStream()); + return in; + } + + public String getResolvedUrl() { + return resolvedUrl; + } +}