| /* |
| * 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.cauldron.bld.ivy; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URI; |
| import java.net.URL; |
| import java.text.ParseException; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.regex.Pattern; |
| |
| import org.apache.ivy.Ivy; |
| import org.apache.ivy.core.IvyContext; |
| import org.apache.ivy.core.module.descriptor.Artifact; |
| import org.apache.ivy.core.module.descriptor.Configuration; |
| import org.apache.ivy.core.module.descriptor.DefaultDependencyArtifactDescriptor; |
| import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor; |
| import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor; |
| import org.apache.ivy.core.module.descriptor.DependencyDescriptor; |
| import org.apache.ivy.core.module.descriptor.ModuleDescriptor; |
| import org.apache.ivy.core.module.id.ModuleRevisionId; |
| import org.apache.ivy.core.settings.IvySettings; |
| import org.apache.ivy.plugins.parser.ModuleDescriptorParser; |
| import org.apache.ivy.plugins.parser.ModuleDescriptorParserRegistry; |
| import org.apache.ivy.plugins.parser.ParserSettings; |
| import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser; |
| import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorWriter; |
| import org.apache.ivy.plugins.repository.Resource; |
| import org.apache.ivy.plugins.repository.file.FileResource; |
| import org.apache.ivy.plugins.repository.url.URLResource; |
| import org.apache.ivy.plugins.resolver.DependencyResolver; |
| import org.cauldron.bld.config.BldFactory; |
| import org.cauldron.bld.config.IBldProject; |
| import org.cauldron.sigil.model.IModelElement; |
| import org.cauldron.sigil.model.common.VersionRange; |
| import org.cauldron.sigil.model.eclipse.ISigilBundle; |
| import org.cauldron.sigil.model.osgi.IBundleModelElement; |
| import org.cauldron.sigil.model.osgi.IPackageImport; |
| import org.cauldron.sigil.model.osgi.IRequiredBundle; |
| import org.cauldron.sigil.repository.IResolution; |
| |
| public class SigilParser implements ModuleDescriptorParser { |
| |
| private static DelegateParser instance; |
| |
| // used by ProjectRepository |
| static DelegateParser instance() { |
| if (instance == null) |
| throw new IllegalStateException("SigilParser is not instantiated."); |
| return instance; |
| } |
| |
| public SigilParser() { |
| if (instance == null) { |
| instance = new DelegateParser(); |
| } |
| } |
| |
| /** |
| * In IvyDE, IvyContext is not available, so we can't find the SigilResolver. |
| * This allows us to construct one. |
| * @deprecated temporary to support IvyDE |
| */ |
| public void setConfig(String config) { |
| instance.config = config; |
| } |
| |
| /** |
| * sets delegate parser. |
| * If not set, we delegate to the default Ivy parser. |
| * @param type name returned by desired parser's getType() method. |
| */ |
| public void setDelegateType(String type) { |
| for (ModuleDescriptorParser parser : ModuleDescriptorParserRegistry.getInstance().getParsers()) { |
| if (parser.getType().equals(type)) { |
| instance.delegate = parser; |
| break; |
| } |
| } |
| |
| if (instance.delegate == null) { |
| throw new IllegalArgumentException("Can't find parser delegateType=" + type); |
| } |
| } |
| |
| /** |
| * sets default file name the delegate parser accepts. |
| * If not set, defaults to "ivy.xml". |
| * @param name |
| */ |
| public void setDelegateFile(String name) { |
| instance.ivyFile = name; |
| } |
| |
| /** |
| * sets the dependencies to keep from the delegate parser. |
| * If not set, all existing dependencies are dropped. |
| * @param regex pattern matching dependency names to keep. |
| */ |
| public void setKeepDependencies(String regex) { |
| instance.keepDependencyPattern = Pattern.compile(regex); |
| } |
| |
| /** |
| * reduce level of info logging. |
| * @param quiet |
| */ |
| public void setQuiet(String quiet) { |
| instance.quiet = Boolean.parseBoolean(quiet); |
| } |
| |
| /** |
| * sets the SigilResolver we use. |
| * If not set, we use the first SigilResolver we find. |
| * @param name |
| */ |
| public void setResolver(String name) { |
| instance.resolverName = name; |
| } |
| |
| /** |
| * adds override element: <override name="X" pattern="Y" replace="Z"/>. Overrides |
| * Ivy variables using a pattern substitution on the resource directory path. |
| * |
| * @deprecated |
| * This is only needed when a delegate parser expects Ant variables to be set |
| * during the ivy:buildlist task (or the ProjectRepository initialisation), |
| * which they are not. The delegate parser should really be fixed, as otherwise |
| * the ivy:buildlist task won't work without this workaround. |
| */ |
| // e.g. <override name="ant.project.name" pattern=".*/([^/]+)/([^/]+)$" replace="$1-$2"/> |
| public void addConfiguredOverride(Map<String, String> var) { |
| final String name = var.get("name"); |
| final String regex = var.get("pattern"); |
| final String replace = var.get("replace"); |
| |
| if (name == null || regex == null || replace == null) |
| throw new IllegalArgumentException("override must contain name, pattern and replace attributes."); |
| |
| instance.varRegex.put(name, Pattern.compile(regex)); |
| instance.varReplace.put(name, replace); |
| } |
| |
| // implement ModuleDescriptorParser interface by delegation to singleton instance. |
| |
| public boolean accept(Resource res) { |
| return instance.accept(res); |
| } |
| |
| public Artifact getMetadataArtifact(ModuleRevisionId mrid, Resource res) { |
| return instance.getMetadataArtifact(mrid, res); |
| } |
| |
| public String getType() { |
| return instance.getType(); |
| } |
| |
| public ModuleDescriptor parseDescriptor(ParserSettings settings, URL xmlURL, boolean validate) |
| throws ParseException, IOException { |
| return instance.parseDescriptor(settings, xmlURL, validate); |
| } |
| |
| public ModuleDescriptor parseDescriptor(ParserSettings settings, URL xmlURL, Resource res, boolean validate) |
| throws ParseException, IOException { |
| return instance.parseDescriptor(settings, xmlURL, res, validate); |
| } |
| |
| public void toIvyFile(InputStream source, Resource res, File destFile, ModuleDescriptor md) throws ParseException, |
| IOException { |
| instance.toIvyFile(source, res, destFile, md); |
| } |
| |
| /** |
| * delegating parser. |
| * (optionally) removes original dependencies and augments with sigil-determined dependencies. |
| */ |
| static class DelegateParser extends XmlModuleDescriptorParser { |
| |
| private static final String SIGIL_BUILDLIST = IBldProject.PROJECT_FILE; |
| private static final String KEEP_ALL = ".*"; |
| |
| private IBldResolver resolver; |
| private ParserSettings defaultSettings; |
| private Map<String, DefaultModuleDescriptor> rawCache = new HashMap<String, DefaultModuleDescriptor>(); |
| private Map<String, DefaultModuleDescriptor> augmentedCache = new HashMap<String, DefaultModuleDescriptor>(); |
| |
| private HashMap<String, String> varReplace = new HashMap<String, String>(); |
| private HashMap<String, Pattern> varRegex = new HashMap<String, Pattern>(); |
| |
| private ModuleDescriptorParser delegate; |
| private String ivyFile = "ivy.xml"; |
| private String resolverName; |
| private Pattern keepDependencyPattern; |
| private boolean quiet; |
| private String config; |
| |
| @Override |
| public boolean accept(Resource res) { |
| boolean accept = (res instanceof SigilResolver.SigilIvy) |
| || res.getName().endsWith("/" + SIGIL_BUILDLIST) |
| || ((instance.getSigilURI(res) != null) && |
| ((instance.delegate != null ? instance.delegate.accept(res) : false) || super.accept(res))); |
| if (accept) |
| Log.verbose("accepted: " + res); |
| return accept; |
| } |
| |
| @Override |
| public void toIvyFile(InputStream source, Resource res, File destFile, ModuleDescriptor md) |
| throws ParseException, IOException { |
| Log.verbose("writing resource: " + res + " toIvyFile: " + destFile); |
| // source allows us to keep layout and comments, |
| // but this doesn't work if source is not ivy.xml. |
| // So just write file directly from descriptor. |
| try { |
| XmlModuleDescriptorWriter.write(md, destFile); |
| } finally { |
| if (source != null) |
| source.close(); |
| } |
| } |
| |
| @Override |
| public ModuleDescriptor parseDescriptor(ParserSettings settings, URL xmlURL, boolean validate) |
| throws ParseException, IOException { |
| return parseDescriptor(settings, xmlURL, new URLResource(xmlURL), validate); |
| } |
| |
| @Override |
| public ModuleDescriptor parseDescriptor(ParserSettings settings, URL xmlURL, Resource res, boolean validate) |
| throws ParseException, IOException { |
| String cacheKey = xmlURL.toString() + settings.hashCode(); |
| DefaultModuleDescriptor dmd = augmentedCache.get(cacheKey); |
| |
| if (dmd == null) { |
| dmd = rawCache.get(cacheKey); |
| if (dmd == null) { |
| dmd = delegateParse(settings, xmlURL, res, validate); |
| } |
| |
| if (!quiet) |
| Log.info("augmenting module=" + dmd.getModuleRevisionId().getName() + |
| " ant.project.name=" + settings.substitute("${ant.project.name}")); |
| |
| addDependenciesToDescriptor(res, dmd); |
| augmentedCache.put(cacheKey, dmd); |
| |
| Log.verbose("augmented dependencies: " + Arrays.asList(dmd.getDependencies())); |
| } |
| |
| return dmd; |
| } |
| |
| // used by ProjectRepository |
| ModuleDescriptor parseDescriptor(URL projectURL) throws ParseException, IOException { |
| if (defaultSettings == null) { |
| Ivy ivy = IvyContext.getContext().peekIvy(); |
| if (ivy == null) |
| throw new IllegalStateException("can't get default settings - no ivy context."); |
| defaultSettings = ivy.getSettings(); |
| } |
| |
| URL ivyURL = new URL(projectURL, ivyFile); |
| String cacheKey = ivyURL.toString() + defaultSettings.hashCode(); |
| DefaultModuleDescriptor dmd = rawCache.get(cacheKey); |
| |
| if (dmd == null) { |
| URLResource res = new URLResource(ivyURL); |
| // Note: this doesn't contain the augmented dependencies, which is OK, |
| // since the ProjectRepository only needs the id and status. |
| dmd = delegateParse(defaultSettings, ivyURL, res, false); |
| rawCache.put(cacheKey, dmd); |
| } |
| |
| return dmd; |
| } |
| |
| private DefaultModuleDescriptor delegateParse(ParserSettings settings, URL xmlURL, Resource res, |
| boolean validate) throws ParseException, IOException { |
| String resName = res.getName(); |
| |
| if (resName.endsWith("/" + SIGIL_BUILDLIST)) { |
| String name = resName.substring(0, resName.length() - SIGIL_BUILDLIST.length()); |
| res = res.clone(name + ivyFile); |
| xmlURL = new URL(xmlURL, ivyFile); |
| } |
| |
| if (settings instanceof IvySettings) { |
| IvySettings ivySettings = (IvySettings) settings; |
| String dir = new File(res.getName()).getParent(); |
| |
| for (String name : varRegex.keySet()) { |
| Pattern regex = varRegex.get(name); |
| String replace = varReplace.get(name); |
| |
| String value = regex.matcher(dir).replaceAll(replace); |
| |
| Log.debug("overriding variable " + name + "=" + value); |
| ivySettings.setVariable(name, value); |
| } |
| } |
| |
| ModuleDescriptor md = null; |
| if (delegate == null || !delegate.accept(res)) { |
| md = super.parseDescriptor(settings, xmlURL, res, validate); |
| } else { |
| md = delegate.parseDescriptor(settings, xmlURL, res, validate); |
| } |
| |
| return mungeDescriptor(md, res); |
| } |
| |
| /** |
| * clones descriptor and removes dependencies, as descriptor MUST have |
| * 'this' as the parser given to its constructor. |
| * Only dependency names matched by keepDependencyPattern are kept, |
| * as we're going to add our own dependencies later. |
| */ |
| private DefaultModuleDescriptor mungeDescriptor(ModuleDescriptor md, Resource res) { |
| |
| if ((md instanceof DefaultModuleDescriptor) && |
| (md.getParser() == this) && |
| (KEEP_ALL.equals(keepDependencyPattern))) { |
| return (DefaultModuleDescriptor) md; |
| } |
| |
| DefaultModuleDescriptor dmd = new DefaultModuleDescriptor(this, res); |
| |
| dmd.setModuleRevisionId(md.getModuleRevisionId()); |
| dmd.setPublicationDate(md.getPublicationDate()); |
| |
| for (Configuration c : md.getConfigurations()) { |
| String conf = c.getName(); |
| |
| dmd.addConfiguration(c); |
| |
| for (Artifact a : md.getArtifacts(conf)) { |
| dmd.addArtifact(conf, a); |
| } |
| } |
| |
| if (keepDependencyPattern != null) { |
| for (DependencyDescriptor dependency : md.getDependencies()) { |
| String name = dependency.getDependencyId().getName(); |
| if (keepDependencyPattern.matcher(name).matches()) { |
| dmd.addDependency(dependency); |
| } |
| } |
| } |
| |
| return dmd; |
| } |
| |
| /* |
| * find URI to Sigil project file. This assumes that it is in the same |
| * directory as the ivy file. |
| */ |
| private URI getSigilURI(Resource res) { |
| URI uri = null; |
| |
| if (res instanceof URLResource) { |
| URL url = ((URLResource) res).getURL(); |
| uri = URI.create(url.toString()).resolve(IBldProject.PROJECT_FILE); |
| try { |
| InputStream stream = uri.toURL().openStream(); // check file |
| // exists |
| stream.close(); |
| } catch (IOException e) { |
| uri = null; |
| } |
| } else if (res instanceof FileResource) { |
| uri = ((FileResource) res).getFile().toURI().resolve(IBldProject.PROJECT_FILE); |
| if (!(new File(uri).exists())) |
| uri = null; |
| } |
| |
| return uri; |
| } |
| |
| /* |
| * add sigil dependencies to ModuleDescriptor. |
| */ |
| private void addDependenciesToDescriptor(Resource res, DefaultModuleDescriptor md) throws IOException { |
| // FIXME: transitive should be configurable |
| final boolean transitive = true; // ivy default is true |
| final boolean changing = false; |
| final boolean force = false; |
| |
| URI uri = getSigilURI(res); |
| if (uri == null) |
| return; |
| |
| IBldProject project; |
| |
| project = BldFactory.getProject(uri); |
| |
| IBundleModelElement requirements = project.getDependencies(); |
| Log.verbose("requirements: " + Arrays.asList(requirements.children())); |
| |
| // preserve version range for Require-Bundle |
| // XXX: synthesise bundle version range corresponding to package version ranges? |
| HashMap<String, VersionRange> versions = new HashMap<String, VersionRange>(); |
| for (IModelElement child : requirements.children()) { |
| if (child instanceof IRequiredBundle) { |
| IRequiredBundle bundle = (IRequiredBundle) child; |
| versions.put(bundle.getSymbolicName(), bundle.getVersions()); |
| } |
| } |
| |
| IBldResolver resolver = findResolver(); |
| if (resolver == null) { |
| // this can happen in IvyDE, but it retries and is OK next time. |
| Log.warn("failed to find resolver - IvyContext not yet available."); |
| return; |
| } |
| |
| IResolution resolution = resolver.resolve(requirements, false); |
| Log.verbose("resolution: " + resolution.getBundles()); |
| |
| ModuleRevisionId masterMrid = md.getModuleRevisionId(); |
| DefaultDependencyDescriptor dd; |
| ModuleRevisionId mrid; |
| |
| for (ISigilBundle bundle : resolution.getBundles()) { |
| IBundleModelElement info = bundle.getBundleInfo(); |
| String name = info.getSymbolicName(); |
| |
| if (name == null) { |
| // e.g. SystemProvider with framework=null |
| continue; |
| } |
| |
| if (bundle instanceof ProjectRepository.ProjectBundle) { |
| ProjectRepository.ProjectBundle pb = (ProjectRepository.ProjectBundle) bundle; |
| String org = pb.getOrg(); |
| if (org == null) |
| org = masterMrid.getOrganisation(); |
| String id = pb.getId(); |
| String module = pb.getModule(); |
| String rev = pb.getRevision(); |
| |
| mrid = ModuleRevisionId.newInstance(org, module, rev); |
| dd = new SigilDependencyDescriptor(md, mrid, force, changing, transitive); |
| |
| if (!id.equals(module)) { // non-default artifact |
| dd.addDependencyArtifact(module, |
| new DefaultDependencyArtifactDescriptor(dd, id, "jar", "jar", null, null)); |
| } |
| } else { |
| VersionRange version = versions.get(name); |
| String rev = version != null ? version.toString() : info.getVersion().toString(); |
| mrid = ModuleRevisionId.newInstance(SigilResolver.ORG_SIGIL, name, rev); |
| dd = new SigilDependencyDescriptor(md, mrid, force, changing, transitive); |
| } |
| |
| int nDeps = 0; |
| boolean foundDefault = false; |
| |
| // TODO: make dependency configurations configurable SIGIL-176 |
| for (String conf : md.getConfigurationsNames()) { |
| if (conf.equals("default")) { |
| foundDefault = true; |
| } else if (md.getArtifacts(conf).length == 0) { |
| dd.addDependencyConfiguration(conf, conf + "(default)"); |
| nDeps++; |
| } |
| } |
| |
| if (nDeps > 0) { |
| if (foundDefault) |
| dd.addDependencyConfiguration("default", "default"); |
| } else { |
| dd.addDependencyConfiguration("*", "*"); // all configurations |
| } |
| |
| md.addDependency(dd); |
| } |
| |
| boolean resolved = true; |
| for (IModelElement child : requirements.children()) { |
| ISigilBundle provider = resolution.getProvider(child); |
| if (provider == null) { |
| resolved = false; |
| // this is parse phase, so only log verbose message. |
| // error is produced during resolution phase. |
| Log.verbose("WARN: can't resolve: " + child); |
| |
| String name; |
| String version; |
| if (child instanceof IRequiredBundle) { |
| IRequiredBundle rb = (IRequiredBundle) child; |
| name = rb.getSymbolicName(); |
| version = rb.getVersions().toString(); |
| } else { |
| IPackageImport pi = (IPackageImport) child; |
| name = "import!" + pi.getPackageName(); |
| version = pi.getVersions().toString(); |
| } |
| |
| mrid = ModuleRevisionId.newInstance("!" + SigilResolver.ORG_SIGIL, name, version); |
| dd = new SigilDependencyDescriptor(md, mrid, force, changing, transitive); |
| dd.addDependencyConfiguration("*", "*"); // all |
| // configurations |
| md.addDependency(dd); |
| } |
| } |
| |
| if (!resolved) { |
| // Ivy does not show warnings or errors logged from parse phase, until after resolution. |
| // but <buildlist> ant task doesn't do resolution, so errors would be silently ignored. |
| // Hence, we must use Message.info to ensure this failure is seen. |
| if (!quiet) |
| Log.info("WARN: resolution failed in: " + masterMrid.getName() + ". Use -v for details."); |
| } |
| } |
| |
| /* |
| * find named resolver, or first resolver that implements IBldResolver. |
| */ |
| private IBldResolver findResolver() { |
| if (resolver == null) { |
| Ivy ivy = IvyContext.getContext().peekIvy(); |
| if (ivy != null) { |
| if (resolverName != null) { |
| DependencyResolver r = ivy.getSettings().getResolver(resolverName); |
| if (r == null) { |
| throw new Error("SigilParser: resolver \"" + resolverName + "\" not defined."); |
| } else if (r instanceof IBldResolver) { |
| resolver = (IBldResolver) r; |
| } else { |
| throw new Error("SigilParser: resolver \"" + resolverName + "\" is not a SigilResolver."); |
| } |
| } else { |
| for (Object r : ivy.getSettings().getResolvers()) { |
| if (r instanceof IBldResolver) { |
| resolver = (IBldResolver) r; |
| break; |
| } |
| } |
| } |
| |
| if (resolver == null) { |
| throw new Error("SigilParser: can't find SigilResolver. Check ivysettings.xml."); |
| } |
| } else if (config != null) { |
| Log.warn("creating duplicate resolver to support IvyDE."); |
| resolver = new SigilResolver(); |
| ((SigilResolver)resolver).setConfig(config); |
| } |
| } |
| return resolver; |
| } |
| } |
| |
| } |
| |
| /* |
| * this is only needed so that we can distinguish sigil-added dependencies from |
| * existing ones. |
| */ |
| class SigilDependencyDescriptor extends DefaultDependencyDescriptor { |
| public SigilDependencyDescriptor(DefaultModuleDescriptor md, ModuleRevisionId mrid, boolean force, |
| boolean changing, boolean transitive) { |
| super(md, mrid, force, changing, transitive); |
| } |
| } |