Home

Awesome

veripacks - Verify Package Specifications

What is it?

Veripacks implements some of the ideas from the blog post "Let's turn packages into a module system".

Veripacks allows to specify which classes from a package hierarchy should be accessible, and verify that the specification is met.

This is similar to package-private access in Java, however Veripacks extends this to subpackages, respecting package parent-child dependencies. While usually the package is just a string identifier, Veripacks treats packages in a hierarchical way. For example, foo.bar.baz is a subpackage of foo.bar. That means that exporting a class not only hides other classes from the same package, but also classes from subpackages.

In some cases, Veripacks can be used to replace a separate build module. It aims to be a scalable and composable solution, allowing for multi-layered exports, that is specifying access both for small pieces of code and large functionalities.

Veripacks currently defines the following annotations:

Using an export-all annotation and an export-specific annotation for same element type (class, subpackage) will result in an error.

Access rules

Access rules should be pretty straightforward and work "as expected".

More formally:

Example

Using a bit of an imaginary syntax to make things compact:

package foo.bar.p1 {
  @Export
  class A { ... }

  class B { ... }
}

package foo.bar.p1.sub_p1 {
  class C { ... }
}

package foo.bar.p2 {
  class Test {
    // ok, A is exported
    new A()

    // illegal, B is not exported
    new B()

    // illegal, C is in a subpackage of p1, and p1 only exports A
    new C()
  }
}

How to use it?

Veripacks can be used with any language running on the JVM; while written in Scala, it will work without problems in Java-only projects.

No build plugins or such are needed; just create a new test, with the following body:

def runVeripacksTest() {
  VeripacksBuilder.build
    .verify("foo.bar")
    .throwIfNotOk()
}

(Note: when using Veripacks from Java, the above becomes:

public void runVeripacksTest() {
  VeripacksBuilder$.MODULE$.build()
    .verify("foo.bar")
    .throwIfNotOk();
}

as that's how a Scala object is translated to Java.)

This will throw an exception if there are some specification violations. You can also inspect the result of the verify call, which contains more detailed information (also included in the exception message).

The project files are deployed to Sonatype's OSS public Nexus repository, which is synced to Maven Central:

<!-- Only the annotations -->
<dependency>
    <groupId>org.veripacks</groupId>
    <artifactId>veripacks-annotations_2.11</artifactId>
    <version>0.4.2</version>
</dependency>

<!-- The verifier, has a dependency on the annotations -->
<dependency>
    <groupId>org.veripacks</groupId>
    <artifactId>veripacks-verifier_2.11</artifactId>
    <version>0.4.2</version>
    <scope>test</scope>
</dependency>

Requiring import for 3rd party libraries

It is also possible to constrain the usage of 3rd party packages using Veripacks. When a Veripacks instance is constructed, it is possible to specify additional packages for which import is required:

VeripacksBuilder
    .requireImportOf("org.hibernate")
    .requireImportOf("com.softwaremill")
    .build
    .verify("com.company.project")
    .throwIfNotOk()

Then if a Hibernate class is used in a class in the com.company.project package (or a child package), its usage is verified. That is, some package must contain the @Import("org.hibernate") annotation (the import may be of any child package, so when using only commons, @Import("org.hibernate.common") will work as well).

The package names are checked by prefix, so to check usages of all com packages, just invoke requireImportOf("com"). To exclude a class, invoke doNotRequireImportOf("com.softwaremill") before including "com". Filters defined earlier have precedence.

This is similar to creating a separate build-module and adding the Hibernate dependency to it only.

Specifying a custom metadata reader

Sometimes it may be desirable to specify a custom metadata reader. For example, if the package naming convention in a project is com.<company>.<project>.<main module>.<submodule>, instead of adding an @RequiresImport annotation to each main-module package, it would be better to automatically require import for such packages.

We may achieve this by extending the CustomAccessDefinitionsReader trait and providing it when building the Veripacks instance:

VeripacksBuilder
  .withCustomAccessDefinitionReader(new CustomAccessDefinitionsReader {
    override def isRequiresImport(pkg: Pkg) = pkg.name.split(".").length == 4
  })
  .build
  .verify(List("com.<company>.<project>"))
  .throwIfNotOk()

What's next?

The first point will allow to further constrain usage of external libraries. For example, if using Hibernate, we could specify that only classes from the org.hibernate package should be accessible, while classes from org.hibernate.internal - not.

Sub- and child- packages

Vocabulary:

Blogs about Veripacks

Notes

Veripacks is also used to verify itself - the code contains some @Export annotations, usage of which is verified by the single test in the self-test module.

Other tools, like Classycle or Structure 101 also allow similar verification to be done. Veripacks differs mainly by:

Licensed under Apache2.

Version 0.4.3 (2 Jun 2015)

Version 0.4.2 (6 Oct 2014)

Version 0.4.1 (2 May 2014)

Version 0.4 (5 August 2013)

Version 0.3 (6 March 2013)

Version 0.2 (3 February 2013)

Version 0.1 (5 January 2013)