You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2021/09/15 09:02:01 UTC

[sling-scriptingbundle-maven-plugin] branch issue/SLING-10811 created (now 3552a28)

This is an automated email from the ASF dual-hosted git repository.

radu pushed a change to branch issue/SLING-10811
in repository https://gitbox.apache.org/repos/asf/sling-scriptingbundle-maven-plugin.git.


      at 3552a28  SLING-10811 - Mark unresolved required capabilities as optional

This branch includes the following new commits:

     new 3552a28  SLING-10811 - Mark unresolved required capabilities as optional

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[sling-scriptingbundle-maven-plugin] 01/01: SLING-10811 - Mark unresolved required capabilities as optional

Posted by ra...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

radu pushed a commit to branch issue/SLING-10811
in repository https://gitbox.apache.org/repos/asf/sling-scriptingbundle-maven-plugin.git

commit 3552a28c62ae165a963198b25e70ff361f2f2296
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Wed Sep 15 11:01:10 2021 +0200

    SLING-10811 - Mark unresolved required capabilities as optional
    
    * required capabilities which are not satisfied by the current project
    are marked as optional; a flag can disable this behaviour, making these
    required capabilities mandatory
---
 .../plugin/bnd/BundledScriptsScannerPlugin.java    | 12 ++++-
 .../plugin/capability/Capabilities.java            | 35 +++++++++----
 .../capability/RequiredResourceTypeCapability.java |  2 +-
 .../scriptingbundle/plugin/maven/MetadataMojo.java | 39 ++++----------
 .../plugin/processor/Constants.java                |  1 +
 src/site/markdown/usage.md.vm                      |  5 ++
 .../scriptingbundle/plugin/AbstractPluginTest.java | 61 +++++++++-------------
 src/test/resources/project-4/bnd.bnd               | 18 +++++++
 src/test/resources/project-4/pom.xml               | 49 +++++++++++++++++
 .../src/main/scripts/apps/components/test/requires |  1 +
 .../main/scripts/apps/components/test/test.html    | 18 +++++++
 11 files changed, 161 insertions(+), 80 deletions(-)

diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPlugin.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPlugin.java
index e94d585..caa3293 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPlugin.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/bnd/BundledScriptsScannerPlugin.java
@@ -93,7 +93,7 @@ public class BundledScriptsScannerPlugin implements AnalyzerPlugin, Plugin {
         scriptEngineMappings = getConfiguredScriptEngineMappings();
         capabilities = Capabilities
                 .fromFileSystemTree(workDirectory, walkPath(workDirectory, includes, excludes), logger,
-                getConfiguredSearchPaths(), scriptEngineMappings);
+                getConfiguredSearchPaths(), scriptEngineMappings, getMissingRequirementsOptional());
         String providedCapabilitiesDefinition = capabilities.getProvidedCapabilitiesString();
         String requiredCapabilitiesDefinition = capabilities.getRequiredCapabilitiesString();
 
@@ -142,7 +142,6 @@ public class BundledScriptsScannerPlugin implements AnalyzerPlugin, Plugin {
     }
 
     private Set<PathMatcher> getConfiguredExcludes() {
-
         String excludesCSV = pluginProperties.get(Constants.BND_EXCLUDES);
         if (StringUtils.isNotEmpty(excludesCSV)) {
             return Collections.unmodifiableSet(Arrays.stream(excludesCSV.split(",")).map(String::trim)
@@ -189,6 +188,15 @@ public class BundledScriptsScannerPlugin implements AnalyzerPlugin, Plugin {
         return Constants.DEFAULT_SEARCH_PATHS;
     }
 
+    private boolean getMissingRequirementsOptional() {
+        String missingRequirementsOptionalString = pluginProperties.get(Constants.BND_MISSING_REQUIREMENTS_OPTIONAL);
+        if (missingRequirementsOptionalString != null) {
+            missingRequirementsOptionalString = missingRequirementsOptionalString.trim().toLowerCase();
+            return !"false".equals(missingRequirementsOptionalString);
+        }
+        return true;
+    }
+
     private Stream<Path> walkPath(Path path, Set<PathMatcher> includes, Set<PathMatcher> excludes) throws IOException {
         return Files.walk(path).filter(file -> {
             boolean include = false;
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/Capabilities.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/Capabilities.java
index d8fc5cb..bd8e4de 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/Capabilities.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/Capabilities.java
@@ -21,7 +21,6 @@ package org.apache.sling.scriptingbundle.plugin.capability;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Optional;
@@ -45,7 +44,6 @@ public class Capabilities {
     private final Set<ProvidedResourceTypeCapability> providedResourceTypeCapabilities;
     private final Set<ProvidedScriptCapability> providedScriptCapabilities;
     private final Set<RequiredResourceTypeCapability> requiredResourceTypeCapabilities;
-    private final Set<RequiredResourceTypeCapability> unresolvedRequiredResourceTypeCapabilities;
     public static final Capabilities EMPTY = new Capabilities(Collections.emptySet(), Collections.emptySet(), Collections.emptySet());
 
     public Capabilities(
@@ -55,9 +53,6 @@ public class Capabilities {
         this.providedResourceTypeCapabilities = providedResourceTypeCapabilities;
         this.providedScriptCapabilities = providedScriptCapabilities;
         this.requiredResourceTypeCapabilities = requiredResourceTypeCapabilities;
-        unresolvedRequiredResourceTypeCapabilities = new HashSet<>(requiredResourceTypeCapabilities);
-        providedResourceTypeCapabilities.forEach(providedResourceTypeCapability -> unresolvedRequiredResourceTypeCapabilities
-                .removeIf(requiredResourceTypeCapability -> requiredResourceTypeCapability.isSatisfied(providedResourceTypeCapability)));
     }
 
     public @NotNull Set<ProvidedResourceTypeCapability> getProvidedResourceTypeCapabilities() {
@@ -72,10 +67,6 @@ public class Capabilities {
         return Collections.unmodifiableSet(requiredResourceTypeCapabilities);
     }
 
-    public @NotNull Set<RequiredResourceTypeCapability> getUnresolvedRequiredResourceTypeCapabilities() {
-        return Collections.unmodifiableSet(unresolvedRequiredResourceTypeCapabilities);
-    }
-
     public @NotNull String getProvidedCapabilitiesString() {
         Parameters parameters = new Parameters();
         for (ProvidedResourceTypeCapability capability : getProvidedResourceTypeCapabilities()) {
@@ -139,7 +130,9 @@ public class Capabilities {
     }
 
     public static @NotNull Capabilities fromFileSystemTree(@NotNull Path root, @NotNull Stream<Path> files, @NotNull Logger logger,
-                                                           @NotNull Set<String> searchPaths, @NotNull Map<String, String> scriptEngineMappings) {
+                                                           @NotNull Set<String> searchPaths,
+                                                           @NotNull Map<String, String> scriptEngineMappings,
+                                                           boolean missingRequirementsOptional) {
         Set<ProvidedResourceTypeCapability> providedResourceTypeCapabilities = new LinkedHashSet<>();
         Set<ProvidedScriptCapability> providedScriptCapabilities = new LinkedHashSet<>();
         Set<RequiredResourceTypeCapability> requiredResourceTypeCapabilities = new LinkedHashSet<>();
@@ -158,7 +151,27 @@ public class Capabilities {
                 requiredResourceTypeCapabilities.addAll(pathCapabilities.getRequiredResourceTypeCapabilities());
             }
         });
-        return new Capabilities(providedResourceTypeCapabilities, providedScriptCapabilities, requiredResourceTypeCapabilities);
+        final Set<RequiredResourceTypeCapability> required = new LinkedHashSet<>();
+        if (missingRequirementsOptional) {
+            Set<RequiredResourceTypeCapability> unresolvedRequiredResourceTypeCapabilities =
+                    new LinkedHashSet<>(requiredResourceTypeCapabilities);
+            providedResourceTypeCapabilities.forEach(providedResourceTypeCapability -> unresolvedRequiredResourceTypeCapabilities
+                    .removeIf(
+                            requiredResourceTypeCapability -> requiredResourceTypeCapability.isSatisfied(providedResourceTypeCapability)));
+
+            requiredResourceTypeCapabilities.forEach(requiredResourceTypeCapability -> {
+                if (unresolvedRequiredResourceTypeCapabilities.contains(requiredResourceTypeCapability)) {
+                    required.add(
+                            RequiredResourceTypeCapability.builder().withResourceType(requiredResourceTypeCapability.getResourceType())
+                                    .withVersionRange(requiredResourceTypeCapability.getVersionRange()).withIsOptional().build());
+                } else {
+                    required.add(requiredResourceTypeCapability);
+                }
+            });
+        } else {
+            required.addAll(requiredResourceTypeCapabilities);
+        }
+        return new Capabilities(providedResourceTypeCapabilities, providedScriptCapabilities, required);
     }
 
 }
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/RequiredResourceTypeCapability.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/RequiredResourceTypeCapability.java
index 7fbab3b..b7a2df2 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/RequiredResourceTypeCapability.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/capability/RequiredResourceTypeCapability.java
@@ -78,7 +78,7 @@ public class RequiredResourceTypeCapability {
 
     @Override
     public String toString() {
-        return String.format("%s{resourceType=%s, versionRange=%s, isOptonal=%s}", this.getClass().getSimpleName(),
+        return String.format("%s{resourceType=%s, versionRange=%s, isOptional=%s}", this.getClass().getSimpleName(),
                 resourceType, versionRange, isOptional);
     }
 
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojo.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojo.java
index f72eaad..f577cf2 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojo.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/maven/MetadataMojo.java
@@ -171,6 +171,14 @@ public class MetadataMojo extends AbstractMojo {
     @Parameter(property = "scriptingbundle.searchPaths")
     private Set<String> searchPaths;
 
+    /**
+     * When set to "true", the requirements which are not satisfied directly by this project will be marked as optional.
+     *
+     * @since 0.5.0
+     */
+    @Parameter(property = "scriptingbundle.missingRequirementsOptional", defaultValue = "true")
+    private boolean missingRequirementsOptional = true;
+
     private Capabilities capabilities;
 
     public void execute() {
@@ -221,18 +229,15 @@ public class MetadataMojo extends AbstractMojo {
                 scannerPaths.stream().map(workDirectory::resolve),
                 logger,
                 searchPaths,
-                scriptEngineMappings
+                scriptEngineMappings,
+                missingRequirementsOptional
             );
             String providedCapabilitiesDefinition = capabilities.getProvidedCapabilitiesString();
             String requiredCapabilitiesDefinition = capabilities.getRequiredCapabilitiesString();
-            String unresolvedRequiredCapabilitiesDefinition = getUnresolvedRequiredCapabilitiesString(capabilities);
             project.getProperties().put("org.apache.sling.scriptingbundle.maven.plugin." + org.osgi.framework.Constants.PROVIDE_CAPABILITY,
                     providedCapabilitiesDefinition);
             project.getProperties().put("org.apache.sling.scriptingbundle.maven.plugin." + org.osgi.framework.Constants.REQUIRE_CAPABILITY,
                     requiredCapabilitiesDefinition);
-            project.getProperties()
-                    .put("org.apache.sling.scriptingbundle.maven.plugin.Unresolved-" + org.osgi.framework.Constants.REQUIRE_CAPABILITY,
-                            unresolvedRequiredCapabilitiesDefinition);
         } catch (IOException e) {
             logger.error("Unable to generate working directory.", e);
         }
@@ -256,30 +261,6 @@ public class MetadataMojo extends AbstractMojo {
         return scanner;
     }
 
-    @NotNull
-    private String getUnresolvedRequiredCapabilitiesString(Capabilities capabilities) {
-        StringBuilder builder = new StringBuilder();
-        int pcNum = capabilities.getUnresolvedRequiredResourceTypeCapabilities().size();
-        int pcIndex = 0;
-        for (RequiredResourceTypeCapability capability : capabilities.getUnresolvedRequiredResourceTypeCapabilities()) {
-            builder.append(Constants.CAPABILITY_NS).append(";");
-            builder.append(ServletResolverConstants.SLING_SERVLET_RESOURCE_TYPES).append("=").append(capability.getResourceType());
-            VersionRange versionRange = capability.getVersionRange();
-            if (versionRange != null) {
-                Version left = versionRange.getLeft();
-                if (versionRange.getLeftType() == VersionRange.LEFT_OPEN) {
-                    left = new Version(left.getMajor(), left.getMinor(), left.getMicro() + 1);
-                }
-                builder.append(";").append(Constants.CAPABILITY_VERSION_AT).append("=").append(left);
-            }
-            if (pcIndex < pcNum - 1) {
-                builder.append(",");
-            }
-            pcIndex++;
-        }
-        return builder.toString();
-    }
-
     Capabilities getCapabilities() {
         return capabilities;
     }
diff --git a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/Constants.java b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/Constants.java
index c9da665..55cbc89 100644
--- a/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/Constants.java
+++ b/src/main/java/org/apache/sling/scriptingbundle/plugin/processor/Constants.java
@@ -52,6 +52,7 @@ public final class Constants {
     public static final String BND_INCLUDES = "includes";
     public static final String BND_SCRIPT_ENGINE_MAPPINGS = "scriptEngineMappings";
     public static final String BND_SEARCH_PATHS = "searchPaths";
+    public static final String BND_MISSING_REQUIREMENTS_OPTIONAL = "missingRequirementsOptional";
 
     public static final Map<String, String> DEFAULT_EXTENSION_TO_SCRIPT_ENGINE_MAPPING;
     public static final Set<String> DEFAULT_SEARCH_PATHS;
diff --git a/src/site/markdown/usage.md.vm b/src/site/markdown/usage.md.vm
index 626e289..b1bec3e 100644
--- a/src/site/markdown/usage.md.vm
+++ b/src/site/markdown/usage.md.vm
@@ -36,6 +36,11 @@ page. In addition to the normal way of structuring scripts in the file tree, the
      is defined as a package name, the resource type label will be the last subpackage (i.e. for `com.mydomain.components.image`, the
      resource type label will be `image`).
 
+Starting with version 0.5.0, the plugin will mark the requirements which are not satisfied by the analysed project as optional; classpath
+dependencies are not checked for provided capabilities. If you want to generate mandatory requirements, set the `missingRequirementsOptional`
+flag to `false`.
+
+
 $h3 Defining scripts
 As an example, let's assume the following layout:
 ```
diff --git a/src/test/java/org/apache/sling/scriptingbundle/plugin/AbstractPluginTest.java b/src/test/java/org/apache/sling/scriptingbundle/plugin/AbstractPluginTest.java
index fa35391..9a96b9b 100644
--- a/src/test/java/org/apache/sling/scriptingbundle/plugin/AbstractPluginTest.java
+++ b/src/test/java/org/apache/sling/scriptingbundle/plugin/AbstractPluginTest.java
@@ -135,7 +135,7 @@ public abstract class AbstractPluginTest {
     
             Set<RequiredResourceTypeCapability> rExpected = new HashSet<>(Arrays.asList(
                     RequiredResourceTypeCapability.builder().withResourceType("sling/default")
-                            .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).build(),
+                            .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).withIsOptional().build(),
                     RequiredResourceTypeCapability.builder().withResourceType("org/apache/sling/bar").build(),
                     RequiredResourceTypeCapability.builder().withResourceType("org/apache/sling/bar")
                             .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).withIsOptional().build()
@@ -144,11 +144,7 @@ public abstract class AbstractPluginTest {
                     ProvidedScriptCapability.builder(scriptEngineMappings)
                             .withPath("/org.apache.sling.wrongbar/wrongbar.has.too.many.selectors.html").build()
             ));
-            Set<RequiredResourceTypeCapability> urExpected = new HashSet<>(Arrays.asList(
-                    RequiredResourceTypeCapability.builder().withResourceType("sling/default")
-                            .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).build()
-            ));
-            verifyCapabilities(capabilities, pExpected, rExpected, sExpected, urExpected);
+            verifyCapabilities(capabilities, pExpected, rExpected, sExpected);
         } finally {
             cleanUp("project-1");
         }
@@ -174,13 +170,9 @@ public abstract class AbstractPluginTest {
             ));
             Set<RequiredResourceTypeCapability> expectedRequired = new HashSet<>(Arrays.asList(
                     RequiredResourceTypeCapability.builder().withResourceType("sling/scripting/warpDrive")
-                            .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).build()
-            ));
-            Set<RequiredResourceTypeCapability> expectedUnresolvedRequired = new HashSet<>(Arrays.asList(
-                    RequiredResourceTypeCapability.builder().withResourceType("sling/scripting/warpDrive")
-                            .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).build()
+                            .withVersionRange(VersionRange.valueOf("[1.0.0,2.0.0)")).withIsOptional().build()
             ));
-            verifyCapabilities(capabilities, pExpected, expectedRequired, expectedScriptCapabilities, expectedUnresolvedRequired);
+            verifyCapabilities(capabilities, pExpected, expectedRequired, expectedScriptCapabilities);
         } finally {
             cleanUp("project-2");
         }
@@ -198,13 +190,31 @@ public abstract class AbstractPluginTest {
                             .withSelectors(Arrays.asList("selector")).withScriptEngine("htl").withScriptExtension("html")
                             .build()
             ));
-            verifyCapabilities(capabilities, pExpected, Collections.emptySet(), Collections.emptySet(), Collections.emptySet());
+            verifyCapabilities(capabilities, pExpected, Collections.emptySet(), Collections.emptySet());
         } finally {
             cleanUp("project-3");
         }
     }
 
-    private void verifyCapabilities(Capabilities capabilities, Set<ProvidedResourceTypeCapability> pExpected, Set<RequiredResourceTypeCapability> rExpected, Set<ProvidedScriptCapability> sExpected, Set<RequiredResourceTypeCapability> urExpected) {
+    @Test
+    public void testProject4() throws Exception {
+        try {
+            PluginExecution execution = executePluginOnProject("project-4");
+            Capabilities capabilities = execution.getCapabilities();
+            Set<ProvidedResourceTypeCapability> pExpected = new HashSet<>(Arrays.asList(
+                    ProvidedResourceTypeCapability.builder().withResourceTypes("components/test", "/apps/components/test")
+                            .withScriptEngine("htl").withScriptExtension("html").build()
+            ));
+            Set<RequiredResourceTypeCapability> rExpected = new HashSet<>(Arrays.asList(
+                    RequiredResourceTypeCapability.builder().withResourceType("components/testhelper").build()
+            ));
+            verifyCapabilities(capabilities, pExpected, rExpected, Collections.emptySet());
+        } finally {
+            cleanUp("project-4");
+        }
+    }
+
+    private void verifyCapabilities(Capabilities capabilities, Set<ProvidedResourceTypeCapability> pExpected, Set<RequiredResourceTypeCapability> rExpected, Set<ProvidedScriptCapability> sExpected) {
         Set<ProvidedResourceTypeCapability> provided = new HashSet<>(capabilities.getProvidedResourceTypeCapabilities());
         StringBuilder missingProvided = new StringBuilder();
         for (ProvidedResourceTypeCapability capability : pExpected) {
@@ -263,28 +273,5 @@ public abstract class AbstractPluginTest {
         if (extraProvidedScripts.length() > 0) {
             fail(extraProvidedScripts.toString());
         }
-    
-        Set<RequiredResourceTypeCapability> unresolvedRequired =
-                new HashSet<>(capabilities.getUnresolvedRequiredResourceTypeCapabilities());
-        assertEquals(urExpected.size(), unresolvedRequired.size());
-        StringBuilder missingUnresolvedRequired = new StringBuilder();
-        for (RequiredResourceTypeCapability capability : urExpected) {
-            boolean removed = unresolvedRequired.remove(capability);
-            if (!removed) {
-                missingUnresolvedRequired.append("Missing unresolved required capability: ").append(capability.toString())
-                        .append(System.lineSeparator());
-            }
-        }
-        if (missingUnresolvedRequired.length() > 0) {
-            fail(missingUnresolvedRequired.toString());
-        }
-        StringBuilder extraUnresolvedRequired = new StringBuilder();
-        for (RequiredResourceTypeCapability capability : unresolvedRequired) {
-            extraUnresolvedRequired.append("Extra unresolved required capability: ").append(capability.toString())
-                    .append(System.lineSeparator());
-        }
-        if (extraUnresolvedRequired.length() > 0) {
-            fail(extraUnresolvedRequired.toString());
-        }
     }
 }
diff --git a/src/test/resources/project-4/bnd.bnd b/src/test/resources/project-4/bnd.bnd
new file mode 100644
index 0000000..173468c
--- /dev/null
+++ b/src/test/resources/project-4/bnd.bnd
@@ -0,0 +1,18 @@
+# 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.
+-plugin:    org.apache.sling.scriptingbundle.plugin.bnd.BundledScriptsScannerPlugin; \
+            missingRequirementsOptional=false
diff --git a/src/test/resources/project-4/pom.xml b/src/test/resources/project-4/pom.xml
new file mode 100644
index 0000000..4a871d8
--- /dev/null
+++ b/src/test/resources/project-4/pom.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>org.apache.sling</groupId>
+    <artifactId>scriptingbundle-maven-plugin-project-1</artifactId>
+    <version>0.0.1</version>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.sling</groupId>
+                <artifactId>scriptingbundle-maven-plugin</artifactId>
+                <configuration>
+                    <missingRequirementsOptional>false</missingRequirementsOptional>
+                </configuration>
+                <executions>
+                    <execution>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>metadata</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/src/test/resources/project-4/src/main/scripts/apps/components/test/requires b/src/test/resources/project-4/src/main/scripts/apps/components/test/requires
new file mode 100644
index 0000000..3569a01
--- /dev/null
+++ b/src/test/resources/project-4/src/main/scripts/apps/components/test/requires
@@ -0,0 +1 @@
+components/testhelper
diff --git a/src/test/resources/project-4/src/main/scripts/apps/components/test/test.html b/src/test/resources/project-4/src/main/scripts/apps/components/test/test.html
new file mode 100644
index 0000000..2853663
--- /dev/null
+++ b/src/test/resources/project-4/src/main/scripts/apps/components/test/test.html
@@ -0,0 +1,18 @@
+<!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->