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:02 UTC

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

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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->