Walk me through writing a custom assembly.xml descriptor to produce a tar.gz distribution with a bin/lib/conf layout.
answer
- id + formats + includeBaseDirectory
- dependencySets -> lib, scope runtime
- fileSets -> bin/conf with fileMode 0755/0644
- files + filtered for ${} substitution
- tar.gz keeps perms, zip drops +x
basics
~20 sWrite an assembly.xml with an <id>, one or more <formats> (like tar.gz), <fileSets> to copy your scripts/config into bin/ and conf/, and a <dependencySets> to drop dependency jars into lib/. Reference it from the plugin's <descriptors> and run assembly:single.
solid answer
~40 sA custom descriptor is XML conforming to the assembly schema. Top level: an `<id>` (becomes the filename suffix), `<formats>` (e.g. `tar.gz`, `zip`, `dir`), and `<includeBaseDirectory>` to control whether everything sits under a root folder. Then you compose the layout: `<dependencySets>` with `<outputDirectory>lib</outputDirectory>` and `<scope>runtime</scope>` to place dependency jars (plus `useProjectArtifact` for your own jar); `<fileSets>` to copy `src/main/scripts` to `bin` (often with `<fileMode>0755</fileMode>` for executables) and `src/main/resources/conf` to `conf`; `<files>` for individual files like a README with optional `<filtered>true</filtered>` for property substitution. Reference it via `<descriptors><descriptor>src/main/assembly/dist.xml</descriptor></descriptors>` and bind `single` to `package`. tar.gz preserves Unix file modes; zip on its own historically did not.
code
xml · 18 lines<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.2.0">
<id>dist</id>
<formats><format>tar.gz</format></formats>
<includeBaseDirectory>true</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<scope>runtime</scope>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>src/main/scripts</directory>
<outputDirectory>bin</outputDirectory>
<fileMode>0755</fileMode>
</fileSet>
</fileSets>
</assembly>go deeper
Aware a custom assembly.xml exists for non-standard layouts.
Can copy fileSets/dependencySets to build a bin/lib/conf tree.
Handles file modes, filtering, base directory, scope selection, and format trade-offs.
Reuses componentDescriptors across modules and standardizes distribution layout org-wide.
## When you need a custom descriptor The predefined `descriptorRefs` give you fat jars or generic bin/src bundles, but real apps need a **specific directory layout** — typically: ``` myapp-1.0/ bin/ start.sh (executable) lib/ app.jar + all dependency jars conf/ application.yml, logback.xml README.txt, LICENSE ``` That requires your own `assembly.xml`. ## The descriptor anatomy ```xml <assembly xmlns="http://maven.apache.org/ASSEMBLY/2.2.0"> <id>dist</id> <formats> <format>tar.gz</format> <format>zip</format> </formats> <includeBaseDirectory>true</includeBaseDirectory> <baseDirectory>${project.artifactId}-${project.version}</baseDirectory> <dependencySets> <dependencySet> <outputDirectory>lib</outputDirectory> <scope>runtime</scope> <useProjectArtifact>true</useProjectArtifact> </dependencySet> </dependencySets> <fileSets> <fileSet> <directory>src/main/scripts</directory> <outputDirectory>bin</outputDirectory> <fileMode>0755</fileMode> <includes><include>*.sh</include></includes> </fileSet> <fileSet> <directory>src/main/resources/conf</directory> <outputDirectory>conf</outputDirectory> <fileMode>0644</fileMode> </fileSet> </fileSets> <files> <file> <source>README.txt</source> <outputDirectory>/</outputDirectory> <filtered>true</filtered> </file> </files> </assembly> ``` ## Key elements explained - **`<id>`** — required; appended to the artifact name (`myapp-1.0-dist.tar.gz`) and used as the assembly identifier. - **`<formats>`** — one or many. tar.gz/tar.bz2 preserve Unix permissions; `dir` gives an exploded folder for debugging; zip/jar are archives. - **`<includeBaseDirectory>` / `<baseDirectory>`** — whether the archive contents are nested under a single root folder (best practice so unzipping doesn't litter the cwd) and what that folder is named. - **`<dependencySets>`** — pulls in dependencies. `<scope>runtime</scope>` selects compile+runtime deps; `useProjectArtifact` (default true) includes your own built jar; you can `<unpack>`, set `<outputFileNameMapping>`, and `<includes>/<excludes>` by GAV. - **`<fileSets>`** — copy whole directories with include/exclude patterns and `<fileMode>` (octal Unix perms — `0755` for scripts, `0644` for data). - **`<files>`** — copy individual files; `<filtered>true</filtered>` substitutes `${...}` Maven properties (e.g. version into README). ## Wiring it into the build ```xml <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>3.7.1</version> <configuration> <descriptors> <descriptor>src/main/assembly/dist.xml</descriptor> </descriptors> </configuration> <executions> <execution> <id>make-dist</id> <phase>package</phase> <goals><goal>single</goal></goals> </execution> </executions> </plugin> ``` ## Gotchas - **File permissions:** `<fileMode>` is honored in tar formats; plain `zip` historically did not store Unix modes, so executables lose their +x bit when extracted on Unix — prefer tar.gz for runnable distributions. - **Line endings:** `<lineEnding>` can normalize CRLF/LF for cross-platform scripts. - **componentDescriptors:** large multi-module projects factor shared fragments into reusable `<componentDescriptors>`.
- Your start.sh loses its executable bit after extraction. What's the likely cause?You packaged as plain zip, which doesn't store Unix permissions, or you didn't set <fileMode>0755</fileMode>. Use tar.gz with fileMode 0755.
- How do you inject the project version into a bundled README?Set <filtered>true</filtered> on the <file> and reference ${project.version} in the README; the assembly plugin runs Maven property filtering on it.
- How do you exclude a specific dependency from the lib folder?Use <excludes> in the dependencySet with the dependency's groupId:artifactId coordinate.
saying these in an interview costs you the question
- Forgetting <id> (it's required and names the output).
- Using zip for a runnable Unix distribution and expecting +x permissions to survive.
- Hardcoding the version in files instead of filtering ${project.version}.