diff --git a/docs/releases/7.0.0/assets/css/style.css b/docs/releases/7.0.0/assets/css/style.css new file mode 100644 index 0000000000..bc2033b3ec --- /dev/null +++ b/docs/releases/7.0.0/assets/css/style.css @@ -0,0 +1,2883 @@ +/*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */ +/** 1. Change the default font family in all browsers (opinionated). 2. Prevent adjustments of font size after orientation changes in IE and iOS. */ +html { font-family: sans-serif; /* 1 */ -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } + +/** Remove the margin in all browsers (opinionated). */ +body { margin: 0; } + +/* HTML5 display definitions ========================================================================== */ +/** Add the correct display in IE 9-. 1. Add the correct display in Edge, IE, and Firefox. 2. Add the correct display in IE. */ +article, aside, details, figcaption, figure, footer, header, main, menu, nav, section { /* 1 */ display: block; } + +summary { display: list-item; } + +/** Add the correct display in IE 9-. */ +audio, canvas, progress, video { display: inline-block; } + +/** Add the correct display in iOS 4-7. */ +audio:not([controls]) { display: none; height: 0; } + +/** Add the correct vertical alignment in Chrome, Firefox, and Opera. */ +progress { vertical-align: baseline; } + +/** Add the correct display in IE 10-. 1. Add the correct display in IE. */ +template, [hidden] { display: none !important; } + +/* Links ========================================================================== */ +/** Remove the gray background on active links in IE 10. */ +a { background-color: transparent; /* 1 */ } + +/** Remove the outline on focused links when they are also active or hovered in all browsers (opinionated). */ +a:active, a:hover { outline-width: 0; } + +/* Text-level semantics ========================================================================== */ +/** 1. Remove the bottom border in Firefox 39-. 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. */ +abbr[title] { border-bottom: none; /* 1 */ text-decoration: underline; /* 2 */ text-decoration: underline dotted; /* 2 */ } + +/** Prevent the duplicate application of `bolder` by the next rule in Safari 6. */ +b, strong { font-weight: inherit; } + +/** Add the correct font weight in Chrome, Edge, and Safari. */ +b, strong { font-weight: bolder; } + +/** Add the correct font style in Android 4.3-. */ +dfn { font-style: italic; } + +/** Correct the font size and margin on `h1` elements within `section` and `article` contexts in Chrome, Firefox, and Safari. */ +h1 { font-size: 2em; margin: 0.67em 0; } + +/** Add the correct background and color in IE 9-. */ +mark { background-color: #ff0; color: #000; } + +/** Add the correct font size in all browsers. */ +small { font-size: 80%; } + +/** Prevent `sub` and `sup` elements from affecting the line height in all browsers. */ +sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } + +sub { bottom: -0.25em; } + +sup { top: -0.5em; } + +/* Embedded content ========================================================================== */ +/** Remove the border on images inside links in IE 10-. */ +img { border-style: none; } + +/** Hide the overflow in IE. */ +svg:not(:root) { overflow: hidden; } + +/* Grouping content ========================================================================== */ +/** 1. Correct the inheritance and scaling of font size in all browsers. 2. Correct the odd `em` font sizing in all browsers. */ +code, kbd, pre, samp { font-family: monospace, monospace; /* 1 */ font-size: 1em; /* 2 */ } + +/** Add the correct margin in IE 8. */ +figure { margin: 1em 40px; } + +/** 1. Add the correct box sizing in Firefox. 2. Show the overflow in Edge and IE. */ +hr { box-sizing: content-box; /* 1 */ height: 0; /* 1 */ overflow: visible; /* 2 */ } + +/* Forms ========================================================================== */ +/** 1. Change font properties to `inherit` in all browsers (opinionated). 2. Remove the margin in Firefox and Safari. */ +button, input, select, textarea { font: inherit; /* 1 */ margin: 0; /* 2 */ } + +/** Restore the font weight unset by the previous rule. */ +optgroup { font-weight: bold; } + +/** Show the overflow in IE. 1. Show the overflow in Edge. */ +button, input { /* 1 */ overflow: visible; } + +/** Remove the inheritance of text transform in Edge, Firefox, and IE. 1. Remove the inheritance of text transform in Firefox. */ +button, select { /* 1 */ text-transform: none; } + +/** 1. Prevent a WebKit bug where (2) destroys native `audio` and `video` controls in Android 4. 2. Correct the inability to style clickable types in iOS and Safari. */ +button, html [type="button"], [type="reset"], [type="submit"] { -webkit-appearance: button; /* 2 */ } + +/** Remove the inner border and padding in Firefox. */ +button::-moz-focus-inner, [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner { border-style: none; padding: 0; } + +/** Restore the focus styles unset by the previous rule. */ +button:-moz-focusring, [type="button"]:-moz-focusring, [type="reset"]:-moz-focusring, [type="submit"]:-moz-focusring { outline: 1px dotted ButtonText; } + +/** Change the border, margin, and padding in all browsers (opinionated). */ +fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } + +/** 1. Correct the text wrapping in Edge and IE. 2. Correct the color inheritance from `fieldset` elements in IE. 3. Remove the padding so developers are not caught out when they zero out `fieldset` elements in all browsers. */ +legend { box-sizing: border-box; /* 1 */ color: inherit; /* 2 */ display: table; /* 1 */ max-width: 100%; /* 1 */ padding: 0; /* 3 */ white-space: normal; /* 1 */ } + +/** Remove the default vertical scrollbar in IE. */ +textarea { overflow: auto; } + +/** 1. Add the correct box sizing in IE 10-. 2. Remove the padding in IE 10-. */ +[type="checkbox"], [type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } + +/** Correct the cursor style of increment and decrement buttons in Chrome. */ +[type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { height: auto; } + +/** 1. Correct the odd appearance in Chrome and Safari. 2. Correct the outline style in Safari. */ +[type="search"] { -webkit-appearance: textfield; /* 1 */ outline-offset: -2px; /* 2 */ } + +/** Remove the inner padding and cancel buttons in Chrome and Safari on OS X. */ +[type="search"]::-webkit-search-cancel-button, [type="search"]::-webkit-search-decoration { -webkit-appearance: none; } + +/** Correct the text style of placeholders in Chrome, Edge, and Safari. */ +::-webkit-input-placeholder { color: inherit; opacity: 0.54; } + +/** 1. Correct the inability to style clickable types in iOS and Safari. 2. Change font properties to `inherit` in Safari. */ +::-webkit-file-upload-button { -webkit-appearance: button; /* 1 */ font: inherit; /* 2 */ } + +* { box-sizing: border-box; } + +input, select, textarea, button { font-family: inherit; font-size: inherit; line-height: inherit; } + +body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 14px; line-height: 1.5; color: #24292e; background-color: #fff; } + +a { color: #0366d6; text-decoration: none; } +a:hover { text-decoration: underline; } + +b, strong { font-weight: 600; } + +hr, .rule { height: 0; margin: 15px 0; overflow: hidden; background: transparent; border: 0; border-bottom: 1px solid #dfe2e5; } +hr::before, .rule::before { display: table; content: ""; } +hr::after, .rule::after { display: table; clear: both; content: ""; } + +table { border-spacing: 0; border-collapse: collapse; } + +td, th { padding: 0; } + +button { cursor: pointer; border-radius: 0; } + +[hidden][hidden] { display: none !important; } + +details summary { cursor: pointer; } +details:not([open]) > *:not(summary) { display: none !important; } + +h1, h2, h3, h4, h5, h6 { margin-top: 0; margin-bottom: 0; } + +h1 { font-size: 32px; font-weight: 600; } + +h2 { font-size: 24px; font-weight: 600; } + +h3 { font-size: 20px; font-weight: 600; } + +h4 { font-size: 16px; font-weight: 600; } + +h5 { font-size: 14px; font-weight: 600; } + +h6 { font-size: 12px; font-weight: 600; } + +p { margin-top: 0; margin-bottom: 10px; } + +small { font-size: 90%; } + +blockquote { margin: 0; } + +ul, ol { padding-left: 0; margin-top: 0; margin-bottom: 0; } + +ol ol, ul ol { list-style-type: lower-roman; } + +ul ul ol, ul ol ol, ol ul ol, ol ol ol { list-style-type: lower-alpha; } + +dd { margin-left: 0; } + +tt, code { font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 12px; } + +pre { margin-top: 0; margin-bottom: 0; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; font-size: 12px; } + +.octicon { vertical-align: text-bottom; } + +/* Fade in an element */ +.anim-fade-in { animation-name: fade-in; animation-duration: 1s; animation-timing-function: ease-in-out; } +.anim-fade-in.fast { animation-duration: 300ms; } + +@keyframes fade-in { 0% { opacity: 0; } + 100% { opacity: 1; } } +/* Fade out an element */ +.anim-fade-out { animation-name: fade-out; animation-duration: 1s; animation-timing-function: ease-out; } +.anim-fade-out.fast { animation-duration: 0.3s; } + +@keyframes fade-out { 0% { opacity: 1; } + 100% { opacity: 0; } } +/* Fade in and slide up an element */ +.anim-fade-up { opacity: 0; animation-name: fade-up; animation-duration: 0.3s; animation-fill-mode: forwards; animation-timing-function: ease-out; animation-delay: 1s; } + +@keyframes fade-up { 0% { opacity: 0.8; transform: translateY(100%); } + 100% { opacity: 1; transform: translateY(0); } } +/* Fade an element out and slide down */ +.anim-fade-down { animation-name: fade-down; animation-duration: 0.3s; animation-fill-mode: forwards; animation-timing-function: ease-in; } + +@keyframes fade-down { 0% { opacity: 1; transform: translateY(0); } + 100% { opacity: 0.5; transform: translateY(100%); } } +/* Grow an element width from 0 to 100% */ +.anim-grow-x { width: 0%; animation-name: grow-x; animation-duration: 0.3s; animation-fill-mode: forwards; animation-timing-function: ease; animation-delay: 0.5s; } + +@keyframes grow-x { to { width: 100%; } } +/* Shrink an element from 100% to 0% */ +.anim-shrink-x { animation-name: shrink-x; animation-duration: 0.3s; animation-fill-mode: forwards; animation-timing-function: ease-in-out; animation-delay: 0.5s; } + +@keyframes shrink-x { to { width: 0%; } } +/* Fade in an element and scale it fast */ +.anim-scale-in { animation-name: scale-in; animation-duration: 0.15s; animation-timing-function: cubic-bezier(0.2, 0, 0.13, 1.5); } + +@keyframes scale-in { 0% { opacity: 0; transform: scale(0.5); } + 100% { opacity: 1; transform: scale(1); } } +/* Pulse an element's opacity */ +.anim-pulse { animation-name: pulse; animation-duration: 2s; animation-timing-function: linear; animation-iteration-count: infinite; } + +@keyframes pulse { 0% { opacity: 0.3; } + 10% { opacity: 1; } + 100% { opacity: 0.3; } } +/* Pulse in an element */ +.anim-pulse-in { animation-name: pulse-in; animation-duration: 0.5s; } + +@keyframes pulse-in { 0% { transform: scale3d(1, 1, 1); } + 50% { transform: scale3d(1.1, 1.1, 1.1); } + 100% { transform: scale3d(1, 1, 1); } } +/* Increase scale of an element on hover */ +.hover-grow { transition: transform 0.3s; backface-visibility: hidden; } +.hover-grow:hover { transform: scale(1.025); } + +/* Add a gray border on all sides */ +.border { border: 1px #e1e4e8 solid !important; } + +/* Add a gray border to the left and right */ +.border-y { border-top: 1px #e1e4e8 solid !important; border-bottom: 1px #e1e4e8 solid !important; } + +/* Remove borders from all sides */ +.border-0 { border: 0 !important; } + +.border-dashed { border-style: dashed !important; } + +/* Use with .border to turn the border blue */ +.border-blue { border-color: #0366d6 !important; } + +/* Use with .border to turn the border blue-light */ +.border-blue-light { border-color: #c8e1ff !important; } + +/* Use with .border to turn the border green */ +.border-green { border-color: #34d058 !important; } + +/* Use with .border to turn the border green light */ +.border-green-light { border-color: #a2cbac !important; } + +/* Use with .border to turn the border red */ +.border-red { border-color: #d73a49 !important; } + +/* Use with .border to turn the border red-light */ +.border-red-light { border-color: #cea0a5 !important; } + +/* Use with .border to turn the border purple */ +.border-purple { border-color: #6f42c1 !important; } + +/* Use with .border to turn the border yellow */ +.border-yellow { border-color: #d9d0a5 !important; } + +/* Use with .border to turn the border gray-light */ +.border-gray-light { border-color: #eaecef !important; } + +/* Use with .border to turn the border gray-dark */ +.border-gray-dark { border-color: #d1d5da !important; } + +/* Use with .border to turn the border rgba black 0.15 */ +.border-black-fade { border-color: rgba(27, 31, 35, 0.15) !important; } + +/* Add a gray border */ +/* Add a gray border to the top */ +.border-top { border-top: 1px #e1e4e8 solid !important; } + +/* Add a gray border to the right */ +.border-right { border-right: 1px #e1e4e8 solid !important; } + +/* Add a gray border to the bottom */ +.border-bottom { border-bottom: 1px #e1e4e8 solid !important; } + +/* Add a gray border to the left */ +.border-left { border-left: 1px #e1e4e8 solid !important; } + +/* Remove the top border */ +.border-top-0 { border-top: 0 !important; } + +/* Remove the right border */ +.border-right-0 { border-right: 0 !important; } + +/* Remove the bottom border */ +.border-bottom-0 { border-bottom: 0 !important; } + +/* Remove the left border */ +.border-left-0 { border-left: 0 !important; } + +/* Remove the border-radius */ +.rounded-0 { border-radius: 0 !important; } + +/* Add a border-radius to all corners */ +.rounded-1 { border-radius: 3px !important; } + +/* Add a 2x border-radius to all corners */ +.rounded-2 { border-radius: 6px !important; } + +.rounded-top-0 { border-top-left-radius: 0 !important; border-top-right-radius: 0 !important; } + +.rounded-top-1 { border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; } + +.rounded-top-2 { border-top-left-radius: 6px !important; border-top-right-radius: 6px !important; } + +.rounded-right-0 { border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; } + +.rounded-right-1 { border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; } + +.rounded-right-2 { border-top-right-radius: 6px !important; border-bottom-right-radius: 6px !important; } + +.rounded-bottom-0 { border-bottom-right-radius: 0 !important; border-bottom-left-radius: 0 !important; } + +.rounded-bottom-1 { border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; } + +.rounded-bottom-2 { border-bottom-right-radius: 6px !important; border-bottom-left-radius: 6px !important; } + +.rounded-left-0 { border-bottom-left-radius: 0 !important; border-top-left-radius: 0 !important; } + +.rounded-left-1 { border-bottom-left-radius: 3px !important; border-top-left-radius: 3px !important; } + +.rounded-left-2 { border-bottom-left-radius: 6px !important; border-top-left-radius: 6px !important; } + +@media (min-width: 544px) { /* Add a gray border */ + /* Add a gray border to the top */ + .border-sm-top { border-top: 1px #e1e4e8 solid !important; } + /* Add a gray border to the right */ + .border-sm-right { border-right: 1px #e1e4e8 solid !important; } + /* Add a gray border to the bottom */ + .border-sm-bottom { border-bottom: 1px #e1e4e8 solid !important; } + /* Add a gray border to the left */ + .border-sm-left { border-left: 1px #e1e4e8 solid !important; } + /* Remove the top border */ + .border-sm-top-0 { border-top: 0 !important; } + /* Remove the right border */ + .border-sm-right-0 { border-right: 0 !important; } + /* Remove the bottom border */ + .border-sm-bottom-0 { border-bottom: 0 !important; } + /* Remove the left border */ + .border-sm-left-0 { border-left: 0 !important; } + /* Remove the border-radius */ + .rounded-sm-0 { border-radius: 0 !important; } + /* Add a border-radius to all corners */ + .rounded-sm-1 { border-radius: 3px !important; } + /* Add a 2x border-radius to all corners */ + .rounded-sm-2 { border-radius: 6px !important; } + .rounded-sm-top-0 { border-top-left-radius: 0 !important; border-top-right-radius: 0 !important; } + .rounded-sm-top-1 { border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; } + .rounded-sm-top-2 { border-top-left-radius: 6px !important; border-top-right-radius: 6px !important; } + .rounded-sm-right-0 { border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; } + .rounded-sm-right-1 { border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; } + .rounded-sm-right-2 { border-top-right-radius: 6px !important; border-bottom-right-radius: 6px !important; } + .rounded-sm-bottom-0 { border-bottom-right-radius: 0 !important; border-bottom-left-radius: 0 !important; } + .rounded-sm-bottom-1 { border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; } + .rounded-sm-bottom-2 { border-bottom-right-radius: 6px !important; border-bottom-left-radius: 6px !important; } + .rounded-sm-left-0 { border-bottom-left-radius: 0 !important; border-top-left-radius: 0 !important; } + .rounded-sm-left-1 { border-bottom-left-radius: 3px !important; border-top-left-radius: 3px !important; } + .rounded-sm-left-2 { border-bottom-left-radius: 6px !important; border-top-left-radius: 6px !important; } } +@media (min-width: 768px) { /* Add a gray border */ + /* Add a gray border to the top */ + .border-md-top { border-top: 1px #e1e4e8 solid !important; } + /* Add a gray border to the right */ + .border-md-right { border-right: 1px #e1e4e8 solid !important; } + /* Add a gray border to the bottom */ + .border-md-bottom { border-bottom: 1px #e1e4e8 solid !important; } + /* Add a gray border to the left */ + .border-md-left { border-left: 1px #e1e4e8 solid !important; } + /* Remove the top border */ + .border-md-top-0 { border-top: 0 !important; } + /* Remove the right border */ + .border-md-right-0 { border-right: 0 !important; } + /* Remove the bottom border */ + .border-md-bottom-0 { border-bottom: 0 !important; } + /* Remove the left border */ + .border-md-left-0 { border-left: 0 !important; } + /* Remove the border-radius */ + .rounded-md-0 { border-radius: 0 !important; } + /* Add a border-radius to all corners */ + .rounded-md-1 { border-radius: 3px !important; } + /* Add a 2x border-radius to all corners */ + .rounded-md-2 { border-radius: 6px !important; } + .rounded-md-top-0 { border-top-left-radius: 0 !important; border-top-right-radius: 0 !important; } + .rounded-md-top-1 { border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; } + .rounded-md-top-2 { border-top-left-radius: 6px !important; border-top-right-radius: 6px !important; } + .rounded-md-right-0 { border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; } + .rounded-md-right-1 { border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; } + .rounded-md-right-2 { border-top-right-radius: 6px !important; border-bottom-right-radius: 6px !important; } + .rounded-md-bottom-0 { border-bottom-right-radius: 0 !important; border-bottom-left-radius: 0 !important; } + .rounded-md-bottom-1 { border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; } + .rounded-md-bottom-2 { border-bottom-right-radius: 6px !important; border-bottom-left-radius: 6px !important; } + .rounded-md-left-0 { border-bottom-left-radius: 0 !important; border-top-left-radius: 0 !important; } + .rounded-md-left-1 { border-bottom-left-radius: 3px !important; border-top-left-radius: 3px !important; } + .rounded-md-left-2 { border-bottom-left-radius: 6px !important; border-top-left-radius: 6px !important; } } +@media (min-width: 1012px) { /* Add a gray border */ + /* Add a gray border to the top */ + .border-lg-top { border-top: 1px #e1e4e8 solid !important; } + /* Add a gray border to the right */ + .border-lg-right { border-right: 1px #e1e4e8 solid !important; } + /* Add a gray border to the bottom */ + .border-lg-bottom { border-bottom: 1px #e1e4e8 solid !important; } + /* Add a gray border to the left */ + .border-lg-left { border-left: 1px #e1e4e8 solid !important; } + /* Remove the top border */ + .border-lg-top-0 { border-top: 0 !important; } + /* Remove the right border */ + .border-lg-right-0 { border-right: 0 !important; } + /* Remove the bottom border */ + .border-lg-bottom-0 { border-bottom: 0 !important; } + /* Remove the left border */ + .border-lg-left-0 { border-left: 0 !important; } + /* Remove the border-radius */ + .rounded-lg-0 { border-radius: 0 !important; } + /* Add a border-radius to all corners */ + .rounded-lg-1 { border-radius: 3px !important; } + /* Add a 2x border-radius to all corners */ + .rounded-lg-2 { border-radius: 6px !important; } + .rounded-lg-top-0 { border-top-left-radius: 0 !important; border-top-right-radius: 0 !important; } + .rounded-lg-top-1 { border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; } + .rounded-lg-top-2 { border-top-left-radius: 6px !important; border-top-right-radius: 6px !important; } + .rounded-lg-right-0 { border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; } + .rounded-lg-right-1 { border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; } + .rounded-lg-right-2 { border-top-right-radius: 6px !important; border-bottom-right-radius: 6px !important; } + .rounded-lg-bottom-0 { border-bottom-right-radius: 0 !important; border-bottom-left-radius: 0 !important; } + .rounded-lg-bottom-1 { border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; } + .rounded-lg-bottom-2 { border-bottom-right-radius: 6px !important; border-bottom-left-radius: 6px !important; } + .rounded-lg-left-0 { border-bottom-left-radius: 0 !important; border-top-left-radius: 0 !important; } + .rounded-lg-left-1 { border-bottom-left-radius: 3px !important; border-top-left-radius: 3px !important; } + .rounded-lg-left-2 { border-bottom-left-radius: 6px !important; border-top-left-radius: 6px !important; } } +@media (min-width: 1280px) { /* Add a gray border */ + /* Add a gray border to the top */ + .border-xl-top { border-top: 1px #e1e4e8 solid !important; } + /* Add a gray border to the right */ + .border-xl-right { border-right: 1px #e1e4e8 solid !important; } + /* Add a gray border to the bottom */ + .border-xl-bottom { border-bottom: 1px #e1e4e8 solid !important; } + /* Add a gray border to the left */ + .border-xl-left { border-left: 1px #e1e4e8 solid !important; } + /* Remove the top border */ + .border-xl-top-0 { border-top: 0 !important; } + /* Remove the right border */ + .border-xl-right-0 { border-right: 0 !important; } + /* Remove the bottom border */ + .border-xl-bottom-0 { border-bottom: 0 !important; } + /* Remove the left border */ + .border-xl-left-0 { border-left: 0 !important; } + /* Remove the border-radius */ + .rounded-xl-0 { border-radius: 0 !important; } + /* Add a border-radius to all corners */ + .rounded-xl-1 { border-radius: 3px !important; } + /* Add a 2x border-radius to all corners */ + .rounded-xl-2 { border-radius: 6px !important; } + .rounded-xl-top-0 { border-top-left-radius: 0 !important; border-top-right-radius: 0 !important; } + .rounded-xl-top-1 { border-top-left-radius: 3px !important; border-top-right-radius: 3px !important; } + .rounded-xl-top-2 { border-top-left-radius: 6px !important; border-top-right-radius: 6px !important; } + .rounded-xl-right-0 { border-top-right-radius: 0 !important; border-bottom-right-radius: 0 !important; } + .rounded-xl-right-1 { border-top-right-radius: 3px !important; border-bottom-right-radius: 3px !important; } + .rounded-xl-right-2 { border-top-right-radius: 6px !important; border-bottom-right-radius: 6px !important; } + .rounded-xl-bottom-0 { border-bottom-right-radius: 0 !important; border-bottom-left-radius: 0 !important; } + .rounded-xl-bottom-1 { border-bottom-right-radius: 3px !important; border-bottom-left-radius: 3px !important; } + .rounded-xl-bottom-2 { border-bottom-right-radius: 6px !important; border-bottom-left-radius: 6px !important; } + .rounded-xl-left-0 { border-bottom-left-radius: 0 !important; border-top-left-radius: 0 !important; } + .rounded-xl-left-1 { border-bottom-left-radius: 3px !important; border-top-left-radius: 3px !important; } + .rounded-xl-left-2 { border-bottom-left-radius: 6px !important; border-top-left-radius: 6px !important; } } +/* Add a 50% border-radius to make something into a circle */ +.circle { border-radius: 50% !important; } + +.box-shadow { box-shadow: 0 1px 1px rgba(27, 31, 35, 0.1) !important; } + +.box-shadow-medium { box-shadow: 0 1px 5px rgba(27, 31, 35, 0.15) !important; } + +.box-shadow-large { box-shadow: 0 1px 15px rgba(27, 31, 35, 0.15) !important; } + +.box-shadow-extra-large { box-shadow: 0 10px 50px rgba(27, 31, 35, 0.07) !important; } + +.box-shadow-none { box-shadow: none !important; } + +/* Set the background to $bg-white */ +.bg-white { background-color: #fff !important; } + +/* Set the background to $bg-blue */ +.bg-blue { background-color: #0366d6 !important; } + +/* Set the background to $bg-blue-light */ +.bg-blue-light { background-color: #f1f8ff !important; } + +/* Set the background to $bg-gray-dark */ +.bg-gray-dark { background-color: #24292e !important; } + +/* Set the background to $bg-gray */ +.bg-gray { background-color: #f6f8fa !important; } + +/* Set the background to $bg-gray-light */ +.bg-gray-light { background-color: #fafbfc !important; } + +/* Set the background to $bg-green */ +.bg-green { background-color: #28a745 !important; } + +/* Set the background to $bg-green-light */ +.bg-green-light { background-color: #dcffe4 !important; } + +/* Set the background to $bg-red */ +.bg-red { background-color: #d73a49 !important; } + +/* Set the background to $bg-red-light */ +.bg-red-light { background-color: #ffdce0 !important; } + +/* Set the background to $bg-yellow */ +.bg-yellow { background-color: #ffd33d !important; } + +/* Set the background to $bg-yellow-light */ +.bg-yellow-light { background-color: #fff5b1 !important; } + +/* Set the background to $bg-purple */ +.bg-purple { background-color: #6f42c1 !important; } + +/* Set the background to $bg-purple-light */ +.bg-purple-light { background-color: #f5f0ff !important; } + +.bg-shade-gradient { background-image: linear-gradient(180deg, rgba(27, 31, 35, 0.065), rgba(27, 31, 35, 0)) !important; background-repeat: no-repeat !important; background-size: 100% 200px !important; } + +/* Set the text color to $text-blue */ +.text-blue { color: #0366d6 !important; } + +/* Set the text color to $text-red */ +.text-red { color: #cb2431 !important; } + +/* Set the text color to $text-gray-light */ +.text-gray-light { color: #6a737d !important; } + +/* Set the text color to $text-gray */ +.text-gray { color: #586069 !important; } + +/* Set the text color to $text-gray-dark */ +.text-gray-dark { color: #24292e !important; } + +/* Set the text color to $text-green */ +.text-green { color: #28a745 !important; } + +/* Set the text color to $text-orange */ +.text-orange { color: #a04100 !important; } + +/* Set the text color to $text-orange-light */ +.text-orange-light { color: #e36209 !important; } + +/* Set the text color to $text-purple */ +.text-purple { color: #6f42c1 !important; } + +/* Set the text color to $text-white */ +.text-white { color: #fff !important; } + +/* Set the text color to inherit */ +.text-inherit { color: inherit !important; } + +.text-pending { color: #b08800 !important; } + +.bg-pending { color: #dbab09 !important; } + +.link-gray { color: #586069 !important; } +.link-gray:hover { color: #0366d6 !important; } + +.link-gray-dark { color: #24292e !important; } +.link-gray-dark:hover { color: #0366d6 !important; } + +/* Set the link color to $text-blue on hover Useful when you want only part of a link to turn blue on hover */ +.link-hover-blue:hover { color: #0366d6 !important; } + +/* Make a link $text-gray, then $text-blue on hover and removes the underline */ +.muted-link { color: #586069 !important; } +.muted-link:hover { color: #0366d6 !important; text-decoration: none; } + +.details-overlay[open] > summary::before { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 80; display: block; cursor: default; content: " "; background: transparent; } + +.details-overlay-dark[open] > summary::before { z-index: 99; background: rgba(27, 31, 35, 0.5); } + +.flex-row { flex-direction: row !important; } + +.flex-row-reverse { flex-direction: row-reverse !important; } + +.flex-column { flex-direction: column !important; } + +.flex-wrap { flex-wrap: wrap !important; } + +.flex-nowrap { flex-wrap: nowrap !important; } + +.flex-justify-start { justify-content: flex-start !important; } + +.flex-justify-end { justify-content: flex-end !important; } + +.flex-justify-center { justify-content: center !important; } + +.flex-justify-between { justify-content: space-between !important; } + +.flex-justify-around { justify-content: space-around !important; } + +.flex-items-start { align-items: flex-start !important; } + +.flex-items-end { align-items: flex-end !important; } + +.flex-items-center { align-items: center !important; } + +.flex-items-baseline { align-items: baseline !important; } + +.flex-items-stretch { align-items: stretch !important; } + +.flex-content-start { align-content: flex-start !important; } + +.flex-content-end { align-content: flex-end !important; } + +.flex-content-center { align-content: center !important; } + +.flex-content-between { align-content: space-between !important; } + +.flex-content-around { align-content: space-around !important; } + +.flex-content-stretch { align-content: stretch !important; } + +.flex-auto { flex: 1 1 auto !important; } + +.flex-shrink-0 { flex-shrink: 0 !important; } + +.flex-self-auto { align-self: auto !important; } + +.flex-self-start { align-self: flex-start !important; } + +.flex-self-end { align-self: flex-end !important; } + +.flex-self-center { align-self: center !important; } + +.flex-self-baseline { align-self: baseline !important; } + +.flex-self-stretch { align-self: stretch !important; } + +.flex-item-equal { flex-grow: 1; flex-basis: 0; } + +@media (min-width: 544px) { .flex-sm-row { flex-direction: row !important; } + .flex-sm-row-reverse { flex-direction: row-reverse !important; } + .flex-sm-column { flex-direction: column !important; } + .flex-sm-wrap { flex-wrap: wrap !important; } + .flex-sm-nowrap { flex-wrap: nowrap !important; } + .flex-sm-justify-start { justify-content: flex-start !important; } + .flex-sm-justify-end { justify-content: flex-end !important; } + .flex-sm-justify-center { justify-content: center !important; } + .flex-sm-justify-between { justify-content: space-between !important; } + .flex-sm-justify-around { justify-content: space-around !important; } + .flex-sm-items-start { align-items: flex-start !important; } + .flex-sm-items-end { align-items: flex-end !important; } + .flex-sm-items-center { align-items: center !important; } + .flex-sm-items-baseline { align-items: baseline !important; } + .flex-sm-items-stretch { align-items: stretch !important; } + .flex-sm-content-start { align-content: flex-start !important; } + .flex-sm-content-end { align-content: flex-end !important; } + .flex-sm-content-center { align-content: center !important; } + .flex-sm-content-between { align-content: space-between !important; } + .flex-sm-content-around { align-content: space-around !important; } + .flex-sm-content-stretch { align-content: stretch !important; } + .flex-sm-auto { flex: 1 1 auto !important; } + .flex-sm-shrink-0 { flex-shrink: 0 !important; } + .flex-sm-self-auto { align-self: auto !important; } + .flex-sm-self-start { align-self: flex-start !important; } + .flex-sm-self-end { align-self: flex-end !important; } + .flex-sm-self-center { align-self: center !important; } + .flex-sm-self-baseline { align-self: baseline !important; } + .flex-sm-self-stretch { align-self: stretch !important; } + .flex-sm-item-equal { flex-grow: 1; flex-basis: 0; } } +@media (min-width: 768px) { .flex-md-row { flex-direction: row !important; } + .flex-md-row-reverse { flex-direction: row-reverse !important; } + .flex-md-column { flex-direction: column !important; } + .flex-md-wrap { flex-wrap: wrap !important; } + .flex-md-nowrap { flex-wrap: nowrap !important; } + .flex-md-justify-start { justify-content: flex-start !important; } + .flex-md-justify-end { justify-content: flex-end !important; } + .flex-md-justify-center { justify-content: center !important; } + .flex-md-justify-between { justify-content: space-between !important; } + .flex-md-justify-around { justify-content: space-around !important; } + .flex-md-items-start { align-items: flex-start !important; } + .flex-md-items-end { align-items: flex-end !important; } + .flex-md-items-center { align-items: center !important; } + .flex-md-items-baseline { align-items: baseline !important; } + .flex-md-items-stretch { align-items: stretch !important; } + .flex-md-content-start { align-content: flex-start !important; } + .flex-md-content-end { align-content: flex-end !important; } + .flex-md-content-center { align-content: center !important; } + .flex-md-content-between { align-content: space-between !important; } + .flex-md-content-around { align-content: space-around !important; } + .flex-md-content-stretch { align-content: stretch !important; } + .flex-md-auto { flex: 1 1 auto !important; } + .flex-md-shrink-0 { flex-shrink: 0 !important; } + .flex-md-self-auto { align-self: auto !important; } + .flex-md-self-start { align-self: flex-start !important; } + .flex-md-self-end { align-self: flex-end !important; } + .flex-md-self-center { align-self: center !important; } + .flex-md-self-baseline { align-self: baseline !important; } + .flex-md-self-stretch { align-self: stretch !important; } + .flex-md-item-equal { flex-grow: 1; flex-basis: 0; } } +@media (min-width: 1012px) { .flex-lg-row { flex-direction: row !important; } + .flex-lg-row-reverse { flex-direction: row-reverse !important; } + .flex-lg-column { flex-direction: column !important; } + .flex-lg-wrap { flex-wrap: wrap !important; } + .flex-lg-nowrap { flex-wrap: nowrap !important; } + .flex-lg-justify-start { justify-content: flex-start !important; } + .flex-lg-justify-end { justify-content: flex-end !important; } + .flex-lg-justify-center { justify-content: center !important; } + .flex-lg-justify-between { justify-content: space-between !important; } + .flex-lg-justify-around { justify-content: space-around !important; } + .flex-lg-items-start { align-items: flex-start !important; } + .flex-lg-items-end { align-items: flex-end !important; } + .flex-lg-items-center { align-items: center !important; } + .flex-lg-items-baseline { align-items: baseline !important; } + .flex-lg-items-stretch { align-items: stretch !important; } + .flex-lg-content-start { align-content: flex-start !important; } + .flex-lg-content-end { align-content: flex-end !important; } + .flex-lg-content-center { align-content: center !important; } + .flex-lg-content-between { align-content: space-between !important; } + .flex-lg-content-around { align-content: space-around !important; } + .flex-lg-content-stretch { align-content: stretch !important; } + .flex-lg-auto { flex: 1 1 auto !important; } + .flex-lg-shrink-0 { flex-shrink: 0 !important; } + .flex-lg-self-auto { align-self: auto !important; } + .flex-lg-self-start { align-self: flex-start !important; } + .flex-lg-self-end { align-self: flex-end !important; } + .flex-lg-self-center { align-self: center !important; } + .flex-lg-self-baseline { align-self: baseline !important; } + .flex-lg-self-stretch { align-self: stretch !important; } + .flex-lg-item-equal { flex-grow: 1; flex-basis: 0; } } +@media (min-width: 1280px) { .flex-xl-row { flex-direction: row !important; } + .flex-xl-row-reverse { flex-direction: row-reverse !important; } + .flex-xl-column { flex-direction: column !important; } + .flex-xl-wrap { flex-wrap: wrap !important; } + .flex-xl-nowrap { flex-wrap: nowrap !important; } + .flex-xl-justify-start { justify-content: flex-start !important; } + .flex-xl-justify-end { justify-content: flex-end !important; } + .flex-xl-justify-center { justify-content: center !important; } + .flex-xl-justify-between { justify-content: space-between !important; } + .flex-xl-justify-around { justify-content: space-around !important; } + .flex-xl-items-start { align-items: flex-start !important; } + .flex-xl-items-end { align-items: flex-end !important; } + .flex-xl-items-center { align-items: center !important; } + .flex-xl-items-baseline { align-items: baseline !important; } + .flex-xl-items-stretch { align-items: stretch !important; } + .flex-xl-content-start { align-content: flex-start !important; } + .flex-xl-content-end { align-content: flex-end !important; } + .flex-xl-content-center { align-content: center !important; } + .flex-xl-content-between { align-content: space-between !important; } + .flex-xl-content-around { align-content: space-around !important; } + .flex-xl-content-stretch { align-content: stretch !important; } + .flex-xl-auto { flex: 1 1 auto !important; } + .flex-xl-shrink-0 { flex-shrink: 0 !important; } + .flex-xl-self-auto { align-self: auto !important; } + .flex-xl-self-start { align-self: flex-start !important; } + .flex-xl-self-end { align-self: flex-end !important; } + .flex-xl-self-center { align-self: center !important; } + .flex-xl-self-baseline { align-self: baseline !important; } + .flex-xl-self-stretch { align-self: stretch !important; } + .flex-xl-item-equal { flex-grow: 1; flex-basis: 0; } } +/* Set position to static */ +.position-static { position: static !important; } + +/* Set position to relative */ +.position-relative { position: relative !important; } + +/* Set position to absolute */ +.position-absolute { position: absolute !important; } + +/* Set position to fixed */ +.position-fixed { position: fixed !important; } + +/* Set top 0 */ +.top-0 { top: 0 !important; } + +/* Set right 0 */ +.right-0 { right: 0 !important; } + +/* Set bottom 0 */ +.bottom-0 { bottom: 0 !important; } + +/* Set left 0 */ +.left-0 { left: 0 !important; } + +/* Vertical align middle */ +.v-align-middle { vertical-align: middle !important; } + +/* Vertical align top */ +.v-align-top { vertical-align: top !important; } + +/* Vertical align bottom */ +.v-align-bottom { vertical-align: bottom !important; } + +/* Vertical align to the top of the text */ +.v-align-text-top { vertical-align: text-top !important; } + +/* Vertical align to the bottom of the text */ +.v-align-text-bottom { vertical-align: text-bottom !important; } + +/* Vertical align to the parent's baseline */ +.v-align-baseline { vertical-align: baseline !important; } + +/* Set the overflow hidden */ +.overflow-hidden { overflow: hidden !important; } + +/* Set the overflow scroll */ +.overflow-scroll { overflow: scroll !important; } + +/* Set the overflow auto */ +.overflow-auto { overflow: auto !important; } + +/* Clear floats around the element */ +.clearfix::before { display: table; content: ""; } +.clearfix::after { display: table; clear: both; content: ""; } + +/* Float to the left */ +.float-left { float: left !important; } + +/* Float to the right */ +.float-right { float: right !important; } + +/* No float */ +.float-none { float: none !important; } + +@media (min-width: 544px) { /* Float to the left */ + .float-sm-left { float: left !important; } + /* Float to the right */ + .float-sm-right { float: right !important; } + /* No float */ + .float-sm-none { float: none !important; } } +@media (min-width: 768px) { /* Float to the left */ + .float-md-left { float: left !important; } + /* Float to the right */ + .float-md-right { float: right !important; } + /* No float */ + .float-md-none { float: none !important; } } +@media (min-width: 1012px) { /* Float to the left */ + .float-lg-left { float: left !important; } + /* Float to the right */ + .float-lg-right { float: right !important; } + /* No float */ + .float-lg-none { float: none !important; } } +@media (min-width: 1280px) { /* Float to the left */ + .float-xl-left { float: left !important; } + /* Float to the right */ + .float-xl-right { float: right !important; } + /* No float */ + .float-xl-none { float: none !important; } } +/* Max width 100% */ +.width-fit { max-width: 100% !important; } + +/* Set the width to 100% */ +.width-full { width: 100% !important; } + +/* Max height 100% */ +.height-fit { max-height: 100% !important; } + +/* Set the height to 100% */ +.height-full { height: 100% !important; } + +/* Remove min-width from element */ +.min-width-0 { min-width: 0 !important; } + +/* Set the direction to rtl */ +.direction-rtl { direction: rtl !important; } + +/* Set the direction to ltr */ +.direction-ltr { direction: ltr !important; } + +@media (min-width: 544px) { /* Set the direction to rtl */ + .direction-sm-rtl { direction: rtl !important; } + /* Set the direction to ltr */ + .direction-sm-ltr { direction: ltr !important; } } +@media (min-width: 768px) { /* Set the direction to rtl */ + .direction-md-rtl { direction: rtl !important; } + /* Set the direction to ltr */ + .direction-md-ltr { direction: ltr !important; } } +@media (min-width: 1012px) { /* Set the direction to rtl */ + .direction-lg-rtl { direction: rtl !important; } + /* Set the direction to ltr */ + .direction-lg-ltr { direction: ltr !important; } } +@media (min-width: 1280px) { /* Set the direction to rtl */ + .direction-xl-rtl { direction: rtl !important; } + /* Set the direction to ltr */ + .direction-xl-ltr { direction: ltr !important; } } +/* Set a $size margin to all sides at $breakpoint */ +.m-0 { margin: 0 !important; } + +/* Set a $size margin on the top at $breakpoint */ +.mt-0 { margin-top: 0 !important; } + +/* Set a $size margin on the right at $breakpoint */ +.mr-0 { margin-right: 0 !important; } + +/* Set a $size margin on the bottom at $breakpoint */ +.mb-0 { margin-bottom: 0 !important; } + +/* Set a $size margin on the left at $breakpoint */ +.ml-0 { margin-left: 0 !important; } + +/* Set a $size margin on the left & right at $breakpoint */ +.mx-0 { margin-right: 0 !important; margin-left: 0 !important; } + +/* Set a $size margin on the top & bottom at $breakpoint */ +.my-0 { margin-top: 0 !important; margin-bottom: 0 !important; } + +/* Set a $size margin to all sides at $breakpoint */ +.m-1 { margin: 4px !important; } + +/* Set a $size margin on the top at $breakpoint */ +.mt-1 { margin-top: 4px !important; } + +/* Set a $size margin on the right at $breakpoint */ +.mr-1 { margin-right: 4px !important; } + +/* Set a $size margin on the bottom at $breakpoint */ +.mb-1 { margin-bottom: 4px !important; } + +/* Set a $size margin on the left at $breakpoint */ +.ml-1 { margin-left: 4px !important; } + +/* Set a negative $size margin on top at $breakpoint */ +.mt-n1 { margin-top: -4px !important; } + +/* Set a negative $size margin on the right at $breakpoint */ +.mr-n1 { margin-right: -4px !important; } + +/* Set a negative $size margin on the bottom at $breakpoint */ +.mb-n1 { margin-bottom: -4px !important; } + +/* Set a negative $size margin on the left at $breakpoint */ +.ml-n1 { margin-left: -4px !important; } + +/* Set a $size margin on the left & right at $breakpoint */ +.mx-1 { margin-right: 4px !important; margin-left: 4px !important; } + +/* Set a $size margin on the top & bottom at $breakpoint */ +.my-1 { margin-top: 4px !important; margin-bottom: 4px !important; } + +/* Set a $size margin to all sides at $breakpoint */ +.m-2 { margin: 8px !important; } + +/* Set a $size margin on the top at $breakpoint */ +.mt-2 { margin-top: 8px !important; } + +/* Set a $size margin on the right at $breakpoint */ +.mr-2 { margin-right: 8px !important; } + +/* Set a $size margin on the bottom at $breakpoint */ +.mb-2 { margin-bottom: 8px !important; } + +/* Set a $size margin on the left at $breakpoint */ +.ml-2 { margin-left: 8px !important; } + +/* Set a negative $size margin on top at $breakpoint */ +.mt-n2 { margin-top: -8px !important; } + +/* Set a negative $size margin on the right at $breakpoint */ +.mr-n2 { margin-right: -8px !important; } + +/* Set a negative $size margin on the bottom at $breakpoint */ +.mb-n2 { margin-bottom: -8px !important; } + +/* Set a negative $size margin on the left at $breakpoint */ +.ml-n2 { margin-left: -8px !important; } + +/* Set a $size margin on the left & right at $breakpoint */ +.mx-2 { margin-right: 8px !important; margin-left: 8px !important; } + +/* Set a $size margin on the top & bottom at $breakpoint */ +.my-2 { margin-top: 8px !important; margin-bottom: 8px !important; } + +/* Set a $size margin to all sides at $breakpoint */ +.m-3 { margin: 16px !important; } + +/* Set a $size margin on the top at $breakpoint */ +.mt-3 { margin-top: 16px !important; } + +/* Set a $size margin on the right at $breakpoint */ +.mr-3 { margin-right: 16px !important; } + +/* Set a $size margin on the bottom at $breakpoint */ +.mb-3 { margin-bottom: 16px !important; } + +/* Set a $size margin on the left at $breakpoint */ +.ml-3 { margin-left: 16px !important; } + +/* Set a negative $size margin on top at $breakpoint */ +.mt-n3 { margin-top: -16px !important; } + +/* Set a negative $size margin on the right at $breakpoint */ +.mr-n3 { margin-right: -16px !important; } + +/* Set a negative $size margin on the bottom at $breakpoint */ +.mb-n3 { margin-bottom: -16px !important; } + +/* Set a negative $size margin on the left at $breakpoint */ +.ml-n3 { margin-left: -16px !important; } + +/* Set a $size margin on the left & right at $breakpoint */ +.mx-3 { margin-right: 16px !important; margin-left: 16px !important; } + +/* Set a $size margin on the top & bottom at $breakpoint */ +.my-3 { margin-top: 16px !important; margin-bottom: 16px !important; } + +/* Set a $size margin to all sides at $breakpoint */ +.m-4 { margin: 24px !important; } + +/* Set a $size margin on the top at $breakpoint */ +.mt-4 { margin-top: 24px !important; } + +/* Set a $size margin on the right at $breakpoint */ +.mr-4 { margin-right: 24px !important; } + +/* Set a $size margin on the bottom at $breakpoint */ +.mb-4 { margin-bottom: 24px !important; } + +/* Set a $size margin on the left at $breakpoint */ +.ml-4 { margin-left: 24px !important; } + +/* Set a negative $size margin on top at $breakpoint */ +.mt-n4 { margin-top: -24px !important; } + +/* Set a negative $size margin on the right at $breakpoint */ +.mr-n4 { margin-right: -24px !important; } + +/* Set a negative $size margin on the bottom at $breakpoint */ +.mb-n4 { margin-bottom: -24px !important; } + +/* Set a negative $size margin on the left at $breakpoint */ +.ml-n4 { margin-left: -24px !important; } + +/* Set a $size margin on the left & right at $breakpoint */ +.mx-4 { margin-right: 24px !important; margin-left: 24px !important; } + +/* Set a $size margin on the top & bottom at $breakpoint */ +.my-4 { margin-top: 24px !important; margin-bottom: 24px !important; } + +/* Set a $size margin to all sides at $breakpoint */ +.m-5 { margin: 32px !important; } + +/* Set a $size margin on the top at $breakpoint */ +.mt-5 { margin-top: 32px !important; } + +/* Set a $size margin on the right at $breakpoint */ +.mr-5 { margin-right: 32px !important; } + +/* Set a $size margin on the bottom at $breakpoint */ +.mb-5 { margin-bottom: 32px !important; } + +/* Set a $size margin on the left at $breakpoint */ +.ml-5 { margin-left: 32px !important; } + +/* Set a negative $size margin on top at $breakpoint */ +.mt-n5 { margin-top: -32px !important; } + +/* Set a negative $size margin on the right at $breakpoint */ +.mr-n5 { margin-right: -32px !important; } + +/* Set a negative $size margin on the bottom at $breakpoint */ +.mb-n5 { margin-bottom: -32px !important; } + +/* Set a negative $size margin on the left at $breakpoint */ +.ml-n5 { margin-left: -32px !important; } + +/* Set a $size margin on the left & right at $breakpoint */ +.mx-5 { margin-right: 32px !important; margin-left: 32px !important; } + +/* Set a $size margin on the top & bottom at $breakpoint */ +.my-5 { margin-top: 32px !important; margin-bottom: 32px !important; } + +/* Set a $size margin to all sides at $breakpoint */ +.m-6 { margin: 40px !important; } + +/* Set a $size margin on the top at $breakpoint */ +.mt-6 { margin-top: 40px !important; } + +/* Set a $size margin on the right at $breakpoint */ +.mr-6 { margin-right: 40px !important; } + +/* Set a $size margin on the bottom at $breakpoint */ +.mb-6 { margin-bottom: 40px !important; } + +/* Set a $size margin on the left at $breakpoint */ +.ml-6 { margin-left: 40px !important; } + +/* Set a negative $size margin on top at $breakpoint */ +.mt-n6 { margin-top: -40px !important; } + +/* Set a negative $size margin on the right at $breakpoint */ +.mr-n6 { margin-right: -40px !important; } + +/* Set a negative $size margin on the bottom at $breakpoint */ +.mb-n6 { margin-bottom: -40px !important; } + +/* Set a negative $size margin on the left at $breakpoint */ +.ml-n6 { margin-left: -40px !important; } + +/* Set a $size margin on the left & right at $breakpoint */ +.mx-6 { margin-right: 40px !important; margin-left: 40px !important; } + +/* Set a $size margin on the top & bottom at $breakpoint */ +.my-6 { margin-top: 40px !important; margin-bottom: 40px !important; } + +/* responsive horizontal auto margins */ +.mx-auto { margin-right: auto !important; margin-left: auto !important; } + +@media (min-width: 544px) { /* Set a $size margin to all sides at $breakpoint */ + .m-sm-0 { margin: 0 !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-sm-0 { margin-top: 0 !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-sm-0 { margin-right: 0 !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-sm-0 { margin-bottom: 0 !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-sm-0 { margin-left: 0 !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-sm-0 { margin-right: 0 !important; margin-left: 0 !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-sm-0 { margin-top: 0 !important; margin-bottom: 0 !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-sm-1 { margin: 4px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-sm-1 { margin-top: 4px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-sm-1 { margin-right: 4px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-sm-1 { margin-bottom: 4px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-sm-1 { margin-left: 4px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-sm-n1 { margin-top: -4px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-sm-n1 { margin-right: -4px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-sm-n1 { margin-bottom: -4px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-sm-n1 { margin-left: -4px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-sm-1 { margin-right: 4px !important; margin-left: 4px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-sm-1 { margin-top: 4px !important; margin-bottom: 4px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-sm-2 { margin: 8px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-sm-2 { margin-top: 8px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-sm-2 { margin-right: 8px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-sm-2 { margin-bottom: 8px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-sm-2 { margin-left: 8px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-sm-n2 { margin-top: -8px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-sm-n2 { margin-right: -8px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-sm-n2 { margin-bottom: -8px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-sm-n2 { margin-left: -8px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-sm-2 { margin-right: 8px !important; margin-left: 8px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-sm-2 { margin-top: 8px !important; margin-bottom: 8px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-sm-3 { margin: 16px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-sm-3 { margin-top: 16px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-sm-3 { margin-right: 16px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-sm-3 { margin-bottom: 16px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-sm-3 { margin-left: 16px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-sm-n3 { margin-top: -16px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-sm-n3 { margin-right: -16px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-sm-n3 { margin-bottom: -16px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-sm-n3 { margin-left: -16px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-sm-3 { margin-right: 16px !important; margin-left: 16px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-sm-3 { margin-top: 16px !important; margin-bottom: 16px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-sm-4 { margin: 24px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-sm-4 { margin-top: 24px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-sm-4 { margin-right: 24px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-sm-4 { margin-bottom: 24px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-sm-4 { margin-left: 24px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-sm-n4 { margin-top: -24px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-sm-n4 { margin-right: -24px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-sm-n4 { margin-bottom: -24px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-sm-n4 { margin-left: -24px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-sm-4 { margin-right: 24px !important; margin-left: 24px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-sm-4 { margin-top: 24px !important; margin-bottom: 24px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-sm-5 { margin: 32px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-sm-5 { margin-top: 32px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-sm-5 { margin-right: 32px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-sm-5 { margin-bottom: 32px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-sm-5 { margin-left: 32px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-sm-n5 { margin-top: -32px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-sm-n5 { margin-right: -32px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-sm-n5 { margin-bottom: -32px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-sm-n5 { margin-left: -32px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-sm-5 { margin-right: 32px !important; margin-left: 32px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-sm-5 { margin-top: 32px !important; margin-bottom: 32px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-sm-6 { margin: 40px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-sm-6 { margin-top: 40px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-sm-6 { margin-right: 40px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-sm-6 { margin-bottom: 40px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-sm-6 { margin-left: 40px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-sm-n6 { margin-top: -40px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-sm-n6 { margin-right: -40px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-sm-n6 { margin-bottom: -40px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-sm-n6 { margin-left: -40px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-sm-6 { margin-right: 40px !important; margin-left: 40px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-sm-6 { margin-top: 40px !important; margin-bottom: 40px !important; } + /* responsive horizontal auto margins */ + .mx-sm-auto { margin-right: auto !important; margin-left: auto !important; } } +@media (min-width: 768px) { /* Set a $size margin to all sides at $breakpoint */ + .m-md-0 { margin: 0 !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-md-0 { margin-top: 0 !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-md-0 { margin-right: 0 !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-md-0 { margin-bottom: 0 !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-md-0 { margin-left: 0 !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-md-0 { margin-right: 0 !important; margin-left: 0 !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-md-0 { margin-top: 0 !important; margin-bottom: 0 !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-md-1 { margin: 4px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-md-1 { margin-top: 4px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-md-1 { margin-right: 4px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-md-1 { margin-bottom: 4px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-md-1 { margin-left: 4px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-md-n1 { margin-top: -4px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-md-n1 { margin-right: -4px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-md-n1 { margin-bottom: -4px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-md-n1 { margin-left: -4px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-md-1 { margin-right: 4px !important; margin-left: 4px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-md-1 { margin-top: 4px !important; margin-bottom: 4px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-md-2 { margin: 8px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-md-2 { margin-top: 8px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-md-2 { margin-right: 8px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-md-2 { margin-bottom: 8px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-md-2 { margin-left: 8px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-md-n2 { margin-top: -8px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-md-n2 { margin-right: -8px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-md-n2 { margin-bottom: -8px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-md-n2 { margin-left: -8px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-md-2 { margin-right: 8px !important; margin-left: 8px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-md-2 { margin-top: 8px !important; margin-bottom: 8px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-md-3 { margin: 16px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-md-3 { margin-top: 16px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-md-3 { margin-right: 16px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-md-3 { margin-bottom: 16px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-md-3 { margin-left: 16px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-md-n3 { margin-top: -16px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-md-n3 { margin-right: -16px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-md-n3 { margin-bottom: -16px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-md-n3 { margin-left: -16px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-md-3 { margin-right: 16px !important; margin-left: 16px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-md-3 { margin-top: 16px !important; margin-bottom: 16px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-md-4 { margin: 24px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-md-4 { margin-top: 24px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-md-4 { margin-right: 24px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-md-4 { margin-bottom: 24px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-md-4 { margin-left: 24px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-md-n4 { margin-top: -24px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-md-n4 { margin-right: -24px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-md-n4 { margin-bottom: -24px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-md-n4 { margin-left: -24px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-md-4 { margin-right: 24px !important; margin-left: 24px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-md-4 { margin-top: 24px !important; margin-bottom: 24px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-md-5 { margin: 32px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-md-5 { margin-top: 32px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-md-5 { margin-right: 32px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-md-5 { margin-bottom: 32px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-md-5 { margin-left: 32px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-md-n5 { margin-top: -32px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-md-n5 { margin-right: -32px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-md-n5 { margin-bottom: -32px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-md-n5 { margin-left: -32px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-md-5 { margin-right: 32px !important; margin-left: 32px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-md-5 { margin-top: 32px !important; margin-bottom: 32px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-md-6 { margin: 40px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-md-6 { margin-top: 40px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-md-6 { margin-right: 40px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-md-6 { margin-bottom: 40px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-md-6 { margin-left: 40px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-md-n6 { margin-top: -40px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-md-n6 { margin-right: -40px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-md-n6 { margin-bottom: -40px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-md-n6 { margin-left: -40px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-md-6 { margin-right: 40px !important; margin-left: 40px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-md-6 { margin-top: 40px !important; margin-bottom: 40px !important; } + /* responsive horizontal auto margins */ + .mx-md-auto { margin-right: auto !important; margin-left: auto !important; } } +@media (min-width: 1012px) { /* Set a $size margin to all sides at $breakpoint */ + .m-lg-0 { margin: 0 !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-lg-0 { margin-top: 0 !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-lg-0 { margin-right: 0 !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-lg-0 { margin-bottom: 0 !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-lg-0 { margin-left: 0 !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-lg-0 { margin-right: 0 !important; margin-left: 0 !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-lg-0 { margin-top: 0 !important; margin-bottom: 0 !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-lg-1 { margin: 4px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-lg-1 { margin-top: 4px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-lg-1 { margin-right: 4px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-lg-1 { margin-bottom: 4px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-lg-1 { margin-left: 4px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-lg-n1 { margin-top: -4px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-lg-n1 { margin-right: -4px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-lg-n1 { margin-bottom: -4px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-lg-n1 { margin-left: -4px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-lg-1 { margin-right: 4px !important; margin-left: 4px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-lg-1 { margin-top: 4px !important; margin-bottom: 4px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-lg-2 { margin: 8px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-lg-2 { margin-top: 8px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-lg-2 { margin-right: 8px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-lg-2 { margin-bottom: 8px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-lg-2 { margin-left: 8px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-lg-n2 { margin-top: -8px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-lg-n2 { margin-right: -8px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-lg-n2 { margin-bottom: -8px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-lg-n2 { margin-left: -8px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-lg-2 { margin-right: 8px !important; margin-left: 8px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-lg-2 { margin-top: 8px !important; margin-bottom: 8px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-lg-3 { margin: 16px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-lg-3 { margin-top: 16px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-lg-3 { margin-right: 16px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-lg-3 { margin-bottom: 16px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-lg-3 { margin-left: 16px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-lg-n3 { margin-top: -16px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-lg-n3 { margin-right: -16px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-lg-n3 { margin-bottom: -16px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-lg-n3 { margin-left: -16px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-lg-3 { margin-right: 16px !important; margin-left: 16px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-lg-3 { margin-top: 16px !important; margin-bottom: 16px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-lg-4 { margin: 24px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-lg-4 { margin-top: 24px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-lg-4 { margin-right: 24px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-lg-4 { margin-bottom: 24px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-lg-4 { margin-left: 24px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-lg-n4 { margin-top: -24px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-lg-n4 { margin-right: -24px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-lg-n4 { margin-bottom: -24px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-lg-n4 { margin-left: -24px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-lg-4 { margin-right: 24px !important; margin-left: 24px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-lg-4 { margin-top: 24px !important; margin-bottom: 24px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-lg-5 { margin: 32px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-lg-5 { margin-top: 32px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-lg-5 { margin-right: 32px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-lg-5 { margin-bottom: 32px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-lg-5 { margin-left: 32px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-lg-n5 { margin-top: -32px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-lg-n5 { margin-right: -32px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-lg-n5 { margin-bottom: -32px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-lg-n5 { margin-left: -32px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-lg-5 { margin-right: 32px !important; margin-left: 32px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-lg-5 { margin-top: 32px !important; margin-bottom: 32px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-lg-6 { margin: 40px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-lg-6 { margin-top: 40px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-lg-6 { margin-right: 40px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-lg-6 { margin-bottom: 40px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-lg-6 { margin-left: 40px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-lg-n6 { margin-top: -40px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-lg-n6 { margin-right: -40px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-lg-n6 { margin-bottom: -40px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-lg-n6 { margin-left: -40px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-lg-6 { margin-right: 40px !important; margin-left: 40px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-lg-6 { margin-top: 40px !important; margin-bottom: 40px !important; } + /* responsive horizontal auto margins */ + .mx-lg-auto { margin-right: auto !important; margin-left: auto !important; } } +@media (min-width: 1280px) { /* Set a $size margin to all sides at $breakpoint */ + .m-xl-0 { margin: 0 !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-xl-0 { margin-top: 0 !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-xl-0 { margin-right: 0 !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-xl-0 { margin-bottom: 0 !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-xl-0 { margin-left: 0 !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-xl-0 { margin-right: 0 !important; margin-left: 0 !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-xl-0 { margin-top: 0 !important; margin-bottom: 0 !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-xl-1 { margin: 4px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-xl-1 { margin-top: 4px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-xl-1 { margin-right: 4px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-xl-1 { margin-bottom: 4px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-xl-1 { margin-left: 4px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-xl-n1 { margin-top: -4px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-xl-n1 { margin-right: -4px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-xl-n1 { margin-bottom: -4px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-xl-n1 { margin-left: -4px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-xl-1 { margin-right: 4px !important; margin-left: 4px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-xl-1 { margin-top: 4px !important; margin-bottom: 4px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-xl-2 { margin: 8px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-xl-2 { margin-top: 8px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-xl-2 { margin-right: 8px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-xl-2 { margin-bottom: 8px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-xl-2 { margin-left: 8px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-xl-n2 { margin-top: -8px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-xl-n2 { margin-right: -8px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-xl-n2 { margin-bottom: -8px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-xl-n2 { margin-left: -8px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-xl-2 { margin-right: 8px !important; margin-left: 8px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-xl-2 { margin-top: 8px !important; margin-bottom: 8px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-xl-3 { margin: 16px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-xl-3 { margin-top: 16px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-xl-3 { margin-right: 16px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-xl-3 { margin-bottom: 16px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-xl-3 { margin-left: 16px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-xl-n3 { margin-top: -16px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-xl-n3 { margin-right: -16px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-xl-n3 { margin-bottom: -16px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-xl-n3 { margin-left: -16px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-xl-3 { margin-right: 16px !important; margin-left: 16px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-xl-3 { margin-top: 16px !important; margin-bottom: 16px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-xl-4 { margin: 24px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-xl-4 { margin-top: 24px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-xl-4 { margin-right: 24px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-xl-4 { margin-bottom: 24px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-xl-4 { margin-left: 24px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-xl-n4 { margin-top: -24px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-xl-n4 { margin-right: -24px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-xl-n4 { margin-bottom: -24px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-xl-n4 { margin-left: -24px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-xl-4 { margin-right: 24px !important; margin-left: 24px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-xl-4 { margin-top: 24px !important; margin-bottom: 24px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-xl-5 { margin: 32px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-xl-5 { margin-top: 32px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-xl-5 { margin-right: 32px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-xl-5 { margin-bottom: 32px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-xl-5 { margin-left: 32px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-xl-n5 { margin-top: -32px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-xl-n5 { margin-right: -32px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-xl-n5 { margin-bottom: -32px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-xl-n5 { margin-left: -32px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-xl-5 { margin-right: 32px !important; margin-left: 32px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-xl-5 { margin-top: 32px !important; margin-bottom: 32px !important; } + /* Set a $size margin to all sides at $breakpoint */ + .m-xl-6 { margin: 40px !important; } + /* Set a $size margin on the top at $breakpoint */ + .mt-xl-6 { margin-top: 40px !important; } + /* Set a $size margin on the right at $breakpoint */ + .mr-xl-6 { margin-right: 40px !important; } + /* Set a $size margin on the bottom at $breakpoint */ + .mb-xl-6 { margin-bottom: 40px !important; } + /* Set a $size margin on the left at $breakpoint */ + .ml-xl-6 { margin-left: 40px !important; } + /* Set a negative $size margin on top at $breakpoint */ + .mt-xl-n6 { margin-top: -40px !important; } + /* Set a negative $size margin on the right at $breakpoint */ + .mr-xl-n6 { margin-right: -40px !important; } + /* Set a negative $size margin on the bottom at $breakpoint */ + .mb-xl-n6 { margin-bottom: -40px !important; } + /* Set a negative $size margin on the left at $breakpoint */ + .ml-xl-n6 { margin-left: -40px !important; } + /* Set a $size margin on the left & right at $breakpoint */ + .mx-xl-6 { margin-right: 40px !important; margin-left: 40px !important; } + /* Set a $size margin on the top & bottom at $breakpoint */ + .my-xl-6 { margin-top: 40px !important; margin-bottom: 40px !important; } + /* responsive horizontal auto margins */ + .mx-xl-auto { margin-right: auto !important; margin-left: auto !important; } } +/* Set a $size padding to all sides at $breakpoint */ +.p-0 { padding: 0 !important; } + +/* Set a $size padding to the top at $breakpoint */ +.pt-0 { padding-top: 0 !important; } + +/* Set a $size padding to the right at $breakpoint */ +.pr-0 { padding-right: 0 !important; } + +/* Set a $size padding to the bottom at $breakpoint */ +.pb-0 { padding-bottom: 0 !important; } + +/* Set a $size padding to the left at $breakpoint */ +.pl-0 { padding-left: 0 !important; } + +/* Set a $size padding to the left & right at $breakpoint */ +.px-0 { padding-right: 0 !important; padding-left: 0 !important; } + +/* Set a $size padding to the top & bottom at $breakpoint */ +.py-0 { padding-top: 0 !important; padding-bottom: 0 !important; } + +/* Set a $size padding to all sides at $breakpoint */ +.p-1 { padding: 4px !important; } + +/* Set a $size padding to the top at $breakpoint */ +.pt-1 { padding-top: 4px !important; } + +/* Set a $size padding to the right at $breakpoint */ +.pr-1 { padding-right: 4px !important; } + +/* Set a $size padding to the bottom at $breakpoint */ +.pb-1 { padding-bottom: 4px !important; } + +/* Set a $size padding to the left at $breakpoint */ +.pl-1 { padding-left: 4px !important; } + +/* Set a $size padding to the left & right at $breakpoint */ +.px-1 { padding-right: 4px !important; padding-left: 4px !important; } + +/* Set a $size padding to the top & bottom at $breakpoint */ +.py-1 { padding-top: 4px !important; padding-bottom: 4px !important; } + +/* Set a $size padding to all sides at $breakpoint */ +.p-2 { padding: 8px !important; } + +/* Set a $size padding to the top at $breakpoint */ +.pt-2 { padding-top: 8px !important; } + +/* Set a $size padding to the right at $breakpoint */ +.pr-2 { padding-right: 8px !important; } + +/* Set a $size padding to the bottom at $breakpoint */ +.pb-2 { padding-bottom: 8px !important; } + +/* Set a $size padding to the left at $breakpoint */ +.pl-2 { padding-left: 8px !important; } + +/* Set a $size padding to the left & right at $breakpoint */ +.px-2 { padding-right: 8px !important; padding-left: 8px !important; } + +/* Set a $size padding to the top & bottom at $breakpoint */ +.py-2 { padding-top: 8px !important; padding-bottom: 8px !important; } + +/* Set a $size padding to all sides at $breakpoint */ +.p-3 { padding: 16px !important; } + +/* Set a $size padding to the top at $breakpoint */ +.pt-3 { padding-top: 16px !important; } + +/* Set a $size padding to the right at $breakpoint */ +.pr-3 { padding-right: 16px !important; } + +/* Set a $size padding to the bottom at $breakpoint */ +.pb-3 { padding-bottom: 16px !important; } + +/* Set a $size padding to the left at $breakpoint */ +.pl-3 { padding-left: 16px !important; } + +/* Set a $size padding to the left & right at $breakpoint */ +.px-3 { padding-right: 16px !important; padding-left: 16px !important; } + +/* Set a $size padding to the top & bottom at $breakpoint */ +.py-3 { padding-top: 16px !important; padding-bottom: 16px !important; } + +/* Set a $size padding to all sides at $breakpoint */ +.p-4 { padding: 24px !important; } + +/* Set a $size padding to the top at $breakpoint */ +.pt-4 { padding-top: 24px !important; } + +/* Set a $size padding to the right at $breakpoint */ +.pr-4 { padding-right: 24px !important; } + +/* Set a $size padding to the bottom at $breakpoint */ +.pb-4 { padding-bottom: 24px !important; } + +/* Set a $size padding to the left at $breakpoint */ +.pl-4 { padding-left: 24px !important; } + +/* Set a $size padding to the left & right at $breakpoint */ +.px-4 { padding-right: 24px !important; padding-left: 24px !important; } + +/* Set a $size padding to the top & bottom at $breakpoint */ +.py-4 { padding-top: 24px !important; padding-bottom: 24px !important; } + +/* Set a $size padding to all sides at $breakpoint */ +.p-5 { padding: 32px !important; } + +/* Set a $size padding to the top at $breakpoint */ +.pt-5 { padding-top: 32px !important; } + +/* Set a $size padding to the right at $breakpoint */ +.pr-5 { padding-right: 32px !important; } + +/* Set a $size padding to the bottom at $breakpoint */ +.pb-5 { padding-bottom: 32px !important; } + +/* Set a $size padding to the left at $breakpoint */ +.pl-5 { padding-left: 32px !important; } + +/* Set a $size padding to the left & right at $breakpoint */ +.px-5 { padding-right: 32px !important; padding-left: 32px !important; } + +/* Set a $size padding to the top & bottom at $breakpoint */ +.py-5 { padding-top: 32px !important; padding-bottom: 32px !important; } + +/* Set a $size padding to all sides at $breakpoint */ +.p-6 { padding: 40px !important; } + +/* Set a $size padding to the top at $breakpoint */ +.pt-6 { padding-top: 40px !important; } + +/* Set a $size padding to the right at $breakpoint */ +.pr-6 { padding-right: 40px !important; } + +/* Set a $size padding to the bottom at $breakpoint */ +.pb-6 { padding-bottom: 40px !important; } + +/* Set a $size padding to the left at $breakpoint */ +.pl-6 { padding-left: 40px !important; } + +/* Set a $size padding to the left & right at $breakpoint */ +.px-6 { padding-right: 40px !important; padding-left: 40px !important; } + +/* Set a $size padding to the top & bottom at $breakpoint */ +.py-6 { padding-top: 40px !important; padding-bottom: 40px !important; } + +@media (min-width: 544px) { /* Set a $size padding to all sides at $breakpoint */ + .p-sm-0 { padding: 0 !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-sm-0 { padding-top: 0 !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-sm-0 { padding-right: 0 !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-sm-0 { padding-bottom: 0 !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-sm-0 { padding-left: 0 !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-sm-0 { padding-right: 0 !important; padding-left: 0 !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-sm-0 { padding-top: 0 !important; padding-bottom: 0 !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-sm-1 { padding: 4px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-sm-1 { padding-top: 4px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-sm-1 { padding-right: 4px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-sm-1 { padding-bottom: 4px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-sm-1 { padding-left: 4px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-sm-1 { padding-right: 4px !important; padding-left: 4px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-sm-1 { padding-top: 4px !important; padding-bottom: 4px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-sm-2 { padding: 8px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-sm-2 { padding-top: 8px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-sm-2 { padding-right: 8px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-sm-2 { padding-bottom: 8px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-sm-2 { padding-left: 8px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-sm-2 { padding-right: 8px !important; padding-left: 8px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-sm-2 { padding-top: 8px !important; padding-bottom: 8px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-sm-3 { padding: 16px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-sm-3 { padding-top: 16px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-sm-3 { padding-right: 16px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-sm-3 { padding-bottom: 16px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-sm-3 { padding-left: 16px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-sm-3 { padding-right: 16px !important; padding-left: 16px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-sm-3 { padding-top: 16px !important; padding-bottom: 16px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-sm-4 { padding: 24px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-sm-4 { padding-top: 24px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-sm-4 { padding-right: 24px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-sm-4 { padding-bottom: 24px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-sm-4 { padding-left: 24px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-sm-4 { padding-right: 24px !important; padding-left: 24px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-sm-4 { padding-top: 24px !important; padding-bottom: 24px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-sm-5 { padding: 32px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-sm-5 { padding-top: 32px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-sm-5 { padding-right: 32px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-sm-5 { padding-bottom: 32px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-sm-5 { padding-left: 32px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-sm-5 { padding-right: 32px !important; padding-left: 32px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-sm-5 { padding-top: 32px !important; padding-bottom: 32px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-sm-6 { padding: 40px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-sm-6 { padding-top: 40px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-sm-6 { padding-right: 40px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-sm-6 { padding-bottom: 40px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-sm-6 { padding-left: 40px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-sm-6 { padding-right: 40px !important; padding-left: 40px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-sm-6 { padding-top: 40px !important; padding-bottom: 40px !important; } } +@media (min-width: 768px) { /* Set a $size padding to all sides at $breakpoint */ + .p-md-0 { padding: 0 !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-md-0 { padding-top: 0 !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-md-0 { padding-right: 0 !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-md-0 { padding-bottom: 0 !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-md-0 { padding-left: 0 !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-md-0 { padding-right: 0 !important; padding-left: 0 !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-md-0 { padding-top: 0 !important; padding-bottom: 0 !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-md-1 { padding: 4px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-md-1 { padding-top: 4px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-md-1 { padding-right: 4px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-md-1 { padding-bottom: 4px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-md-1 { padding-left: 4px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-md-1 { padding-right: 4px !important; padding-left: 4px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-md-1 { padding-top: 4px !important; padding-bottom: 4px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-md-2 { padding: 8px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-md-2 { padding-top: 8px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-md-2 { padding-right: 8px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-md-2 { padding-bottom: 8px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-md-2 { padding-left: 8px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-md-2 { padding-right: 8px !important; padding-left: 8px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-md-2 { padding-top: 8px !important; padding-bottom: 8px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-md-3 { padding: 16px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-md-3 { padding-top: 16px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-md-3 { padding-right: 16px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-md-3 { padding-bottom: 16px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-md-3 { padding-left: 16px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-md-3 { padding-right: 16px !important; padding-left: 16px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-md-3 { padding-top: 16px !important; padding-bottom: 16px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-md-4 { padding: 24px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-md-4 { padding-top: 24px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-md-4 { padding-right: 24px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-md-4 { padding-bottom: 24px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-md-4 { padding-left: 24px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-md-4 { padding-right: 24px !important; padding-left: 24px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-md-4 { padding-top: 24px !important; padding-bottom: 24px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-md-5 { padding: 32px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-md-5 { padding-top: 32px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-md-5 { padding-right: 32px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-md-5 { padding-bottom: 32px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-md-5 { padding-left: 32px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-md-5 { padding-right: 32px !important; padding-left: 32px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-md-5 { padding-top: 32px !important; padding-bottom: 32px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-md-6 { padding: 40px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-md-6 { padding-top: 40px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-md-6 { padding-right: 40px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-md-6 { padding-bottom: 40px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-md-6 { padding-left: 40px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-md-6 { padding-right: 40px !important; padding-left: 40px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-md-6 { padding-top: 40px !important; padding-bottom: 40px !important; } } +@media (min-width: 1012px) { /* Set a $size padding to all sides at $breakpoint */ + .p-lg-0 { padding: 0 !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-lg-0 { padding-top: 0 !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-lg-0 { padding-right: 0 !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-lg-0 { padding-bottom: 0 !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-lg-0 { padding-left: 0 !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-lg-0 { padding-right: 0 !important; padding-left: 0 !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-lg-0 { padding-top: 0 !important; padding-bottom: 0 !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-lg-1 { padding: 4px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-lg-1 { padding-top: 4px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-lg-1 { padding-right: 4px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-lg-1 { padding-bottom: 4px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-lg-1 { padding-left: 4px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-lg-1 { padding-right: 4px !important; padding-left: 4px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-lg-1 { padding-top: 4px !important; padding-bottom: 4px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-lg-2 { padding: 8px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-lg-2 { padding-top: 8px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-lg-2 { padding-right: 8px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-lg-2 { padding-bottom: 8px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-lg-2 { padding-left: 8px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-lg-2 { padding-right: 8px !important; padding-left: 8px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-lg-2 { padding-top: 8px !important; padding-bottom: 8px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-lg-3 { padding: 16px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-lg-3 { padding-top: 16px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-lg-3 { padding-right: 16px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-lg-3 { padding-bottom: 16px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-lg-3 { padding-left: 16px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-lg-3 { padding-right: 16px !important; padding-left: 16px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-lg-3 { padding-top: 16px !important; padding-bottom: 16px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-lg-4 { padding: 24px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-lg-4 { padding-top: 24px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-lg-4 { padding-right: 24px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-lg-4 { padding-bottom: 24px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-lg-4 { padding-left: 24px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-lg-4 { padding-right: 24px !important; padding-left: 24px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-lg-4 { padding-top: 24px !important; padding-bottom: 24px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-lg-5 { padding: 32px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-lg-5 { padding-top: 32px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-lg-5 { padding-right: 32px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-lg-5 { padding-bottom: 32px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-lg-5 { padding-left: 32px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-lg-5 { padding-right: 32px !important; padding-left: 32px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-lg-5 { padding-top: 32px !important; padding-bottom: 32px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-lg-6 { padding: 40px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-lg-6 { padding-top: 40px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-lg-6 { padding-right: 40px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-lg-6 { padding-bottom: 40px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-lg-6 { padding-left: 40px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-lg-6 { padding-right: 40px !important; padding-left: 40px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-lg-6 { padding-top: 40px !important; padding-bottom: 40px !important; } } +@media (min-width: 1280px) { /* Set a $size padding to all sides at $breakpoint */ + .p-xl-0 { padding: 0 !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-xl-0 { padding-top: 0 !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-xl-0 { padding-right: 0 !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-xl-0 { padding-bottom: 0 !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-xl-0 { padding-left: 0 !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-xl-0 { padding-right: 0 !important; padding-left: 0 !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-xl-0 { padding-top: 0 !important; padding-bottom: 0 !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-xl-1 { padding: 4px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-xl-1 { padding-top: 4px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-xl-1 { padding-right: 4px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-xl-1 { padding-bottom: 4px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-xl-1 { padding-left: 4px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-xl-1 { padding-right: 4px !important; padding-left: 4px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-xl-1 { padding-top: 4px !important; padding-bottom: 4px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-xl-2 { padding: 8px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-xl-2 { padding-top: 8px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-xl-2 { padding-right: 8px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-xl-2 { padding-bottom: 8px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-xl-2 { padding-left: 8px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-xl-2 { padding-right: 8px !important; padding-left: 8px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-xl-2 { padding-top: 8px !important; padding-bottom: 8px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-xl-3 { padding: 16px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-xl-3 { padding-top: 16px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-xl-3 { padding-right: 16px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-xl-3 { padding-bottom: 16px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-xl-3 { padding-left: 16px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-xl-3 { padding-right: 16px !important; padding-left: 16px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-xl-3 { padding-top: 16px !important; padding-bottom: 16px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-xl-4 { padding: 24px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-xl-4 { padding-top: 24px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-xl-4 { padding-right: 24px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-xl-4 { padding-bottom: 24px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-xl-4 { padding-left: 24px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-xl-4 { padding-right: 24px !important; padding-left: 24px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-xl-4 { padding-top: 24px !important; padding-bottom: 24px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-xl-5 { padding: 32px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-xl-5 { padding-top: 32px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-xl-5 { padding-right: 32px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-xl-5 { padding-bottom: 32px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-xl-5 { padding-left: 32px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-xl-5 { padding-right: 32px !important; padding-left: 32px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-xl-5 { padding-top: 32px !important; padding-bottom: 32px !important; } + /* Set a $size padding to all sides at $breakpoint */ + .p-xl-6 { padding: 40px !important; } + /* Set a $size padding to the top at $breakpoint */ + .pt-xl-6 { padding-top: 40px !important; } + /* Set a $size padding to the right at $breakpoint */ + .pr-xl-6 { padding-right: 40px !important; } + /* Set a $size padding to the bottom at $breakpoint */ + .pb-xl-6 { padding-bottom: 40px !important; } + /* Set a $size padding to the left at $breakpoint */ + .pl-xl-6 { padding-left: 40px !important; } + /* Set a $size padding to the left & right at $breakpoint */ + .px-xl-6 { padding-right: 40px !important; padding-left: 40px !important; } + /* Set a $size padding to the top & bottom at $breakpoint */ + .py-xl-6 { padding-top: 40px !important; padding-bottom: 40px !important; } } +.p-responsive { padding-right: 16px !important; padding-left: 16px !important; } +@media (min-width: 544px) { .p-responsive { padding-right: 40px !important; padding-left: 40px !important; } } +@media (min-width: 1012px) { .p-responsive { padding-right: 16px !important; padding-left: 16px !important; } } + +/* Set the font size to 26px */ +.h1 { font-size: 26px !important; } +@media (min-width: 768px) { .h1 { font-size: 32px !important; } } + +/* Set the font size to 22px */ +.h2 { font-size: 22px !important; } +@media (min-width: 768px) { .h2 { font-size: 24px !important; } } + +/* Set the font size to 18px */ +.h3 { font-size: 18px !important; } +@media (min-width: 768px) { .h3 { font-size: 20px !important; } } + +/* Set the font size to 16px */ +.h4 { font-size: 16px !important; } + +/* Set the font size to 14px */ +.h5 { font-size: 14px !important; } + +/* Set the font size to 12px */ +.h6 { font-size: 12px !important; } + +.h1, .h2, .h3, .h4, .h5, .h6 { font-weight: 600 !important; } + +/* Set the font size to 26px */ +.f1 { font-size: 26px !important; } +@media (min-width: 768px) { .f1 { font-size: 32px !important; } } + +/* Set the font size to 22px */ +.f2 { font-size: 22px !important; } +@media (min-width: 768px) { .f2 { font-size: 24px !important; } } + +/* Set the font size to 18px */ +.f3 { font-size: 18px !important; } +@media (min-width: 768px) { .f3 { font-size: 20px !important; } } + +/* Set the font size to 16px */ +.f4 { font-size: 16px !important; } +@media (min-width: 768px) { .f4 { font-size: 16px !important; } } + +/* Set the font size to 14px */ +.f5 { font-size: 14px !important; } + +/* Set the font size to 12px */ +.f6 { font-size: 12px !important; } + +/* Set the font size to 40px and weight to light */ +.f00-light { font-size: 40px !important; font-weight: 300 !important; } +@media (min-width: 768px) { .f00-light { font-size: 48px !important; } } + +/* Set the font size to 32px and weight to light */ +.f0-light { font-size: 32px !important; font-weight: 300 !important; } +@media (min-width: 768px) { .f0-light { font-size: 40px !important; } } + +/* Set the font size to 26px and weight to light */ +.f1-light { font-size: 26px !important; font-weight: 300 !important; } +@media (min-width: 768px) { .f1-light { font-size: 32px !important; } } + +/* Set the font size to 22px and weight to light */ +.f2-light { font-size: 22px !important; font-weight: 300 !important; } +@media (min-width: 768px) { .f2-light { font-size: 24px !important; } } + +/* Set the font size to 18px and weight to light */ +.f3-light { font-size: 18px !important; font-weight: 300 !important; } +@media (min-width: 768px) { .f3-light { font-size: 20px !important; } } + +/* Set the font size to ${#h6-size} */ +.text-small { font-size: 12px !important; } + +/* Large leading paragraphs */ +.lead { margin-bottom: 30px; font-size: 20px; font-weight: 300; color: #586069; } + +/* Set the line height to ultra condensed */ +.lh-condensed-ultra { line-height: 1 !important; } + +/* Set the line height to condensed */ +.lh-condensed { line-height: 1.25 !important; } + +/* Set the line height to default */ +.lh-default { line-height: 1.5 !important; } + +/* Set the line height to zero */ +.lh-0 { line-height: 0 !important; } + +/* Text align to the right */ +.text-right { text-align: right !important; } + +/* Text align to the left */ +.text-left { text-align: left !important; } + +/* Text align to the center */ +.text-center { text-align: center !important; } + +@media (min-width: 544px) { /* Text align to the right */ + .text-sm-right { text-align: right !important; } + /* Text align to the left */ + .text-sm-left { text-align: left !important; } + /* Text align to the center */ + .text-sm-center { text-align: center !important; } } +@media (min-width: 768px) { /* Text align to the right */ + .text-md-right { text-align: right !important; } + /* Text align to the left */ + .text-md-left { text-align: left !important; } + /* Text align to the center */ + .text-md-center { text-align: center !important; } } +@media (min-width: 1012px) { /* Text align to the right */ + .text-lg-right { text-align: right !important; } + /* Text align to the left */ + .text-lg-left { text-align: left !important; } + /* Text align to the center */ + .text-lg-center { text-align: center !important; } } +@media (min-width: 1280px) { /* Text align to the right */ + .text-xl-right { text-align: right !important; } + /* Text align to the left */ + .text-xl-left { text-align: left !important; } + /* Text align to the center */ + .text-xl-center { text-align: center !important; } } +/* Set the font weight to normal */ +.text-normal { font-weight: 400 !important; } + +/* Set the font weight to bold */ +.text-bold { font-weight: 600 !important; } + +/* Set the font to italic */ +.text-italic { font-style: italic !important; } + +/* Make text uppercase */ +.text-uppercase { text-transform: uppercase !important; } + +/* Underline text */ +.text-underline { text-decoration: underline !important; } + +/* Don't underline text */ +.no-underline { text-decoration: none !important; } + +/* Don't wrap white space */ +.no-wrap { white-space: nowrap !important; } + +/* Normal white space */ +.ws-normal { white-space: normal !important; } + +/* Allow long lines with no spaces to line break */ +.wb-break-all { word-break: break-all !important; } + +.text-emphasized { font-weight: 600; color: #24292e; } + +.list-style-none { list-style: none !important; } + +/* Add a dark text shadow */ +.text-shadow-dark { text-shadow: 0 1px 1px rgba(27, 31, 35, 0.25), 0 1px 25px rgba(27, 31, 35, 0.75); } + +/* Add a light text shadow */ +.text-shadow-light { text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); } + +/* Set to monospace font */ +.text-mono { font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; } + +/* Disallow user from selecting text */ +.user-select-none { user-select: none !important; } + +.d-block { display: block !important; } + +.d-flex { display: flex !important; } + +.d-inline { display: inline !important; } + +.d-inline-block { display: inline-block !important; } + +.d-inline-flex { display: inline-flex !important; } + +.d-none { display: none !important; } + +.d-table { display: table !important; } + +.d-table-cell { display: table-cell !important; } + +@media (min-width: 544px) { .d-sm-block { display: block !important; } + .d-sm-flex { display: flex !important; } + .d-sm-inline { display: inline !important; } + .d-sm-inline-block { display: inline-block !important; } + .d-sm-inline-flex { display: inline-flex !important; } + .d-sm-none { display: none !important; } + .d-sm-table { display: table !important; } + .d-sm-table-cell { display: table-cell !important; } } +@media (min-width: 768px) { .d-md-block { display: block !important; } + .d-md-flex { display: flex !important; } + .d-md-inline { display: inline !important; } + .d-md-inline-block { display: inline-block !important; } + .d-md-inline-flex { display: inline-flex !important; } + .d-md-none { display: none !important; } + .d-md-table { display: table !important; } + .d-md-table-cell { display: table-cell !important; } } +@media (min-width: 1012px) { .d-lg-block { display: block !important; } + .d-lg-flex { display: flex !important; } + .d-lg-inline { display: inline !important; } + .d-lg-inline-block { display: inline-block !important; } + .d-lg-inline-flex { display: inline-flex !important; } + .d-lg-none { display: none !important; } + .d-lg-table { display: table !important; } + .d-lg-table-cell { display: table-cell !important; } } +@media (min-width: 1280px) { .d-xl-block { display: block !important; } + .d-xl-flex { display: flex !important; } + .d-xl-inline { display: inline !important; } + .d-xl-inline-block { display: inline-block !important; } + .d-xl-inline-flex { display: inline-flex !important; } + .d-xl-none { display: none !important; } + .d-xl-table { display: table !important; } + .d-xl-table-cell { display: table-cell !important; } } +.v-hidden { visibility: hidden !important; } + +.v-visible { visibility: visible !important; } + +@media (max-width: 544px) { .hide-sm { display: none !important; } } +@media (min-width: 544px) and (max-width: 768px) { .hide-md { display: none !important; } } +@media (min-width: 768px) and (max-width: 1012px) { .hide-lg { display: none !important; } } +@media (min-width: 1012px) { .hide-xl { display: none !important; } } +/* Set the table-layout to fixed */ +.table-fixed { table-layout: fixed !important; } + +.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip: rect(0, 0, 0, 0); word-wrap: normal; border: 0; } + +.show-on-focus { position: absolute; width: 1px; height: 1px; margin: 0; overflow: hidden; clip: rect(1px, 1px, 1px, 1px); } +.show-on-focus:focus { z-index: 20; width: auto; height: auto; clip: auto; } + +.container { width: 980px; margin-right: auto; margin-left: auto; } +.container::before { display: table; content: ""; } +.container::after { display: table; clear: both; content: ""; } + +.container-md { max-width: 768px; margin-right: auto; margin-left: auto; } + +.container-lg { max-width: 1012px; margin-right: auto; margin-left: auto; } + +.container-xl { max-width: 1280px; margin-right: auto; margin-left: auto; } + +.columns { margin-right: -10px; margin-left: -10px; } +.columns::before { display: table; content: ""; } +.columns::after { display: table; clear: both; content: ""; } + +.column { float: left; padding-right: 10px; padding-left: 10px; } + +.one-third { width: 33.333333%; } + +.two-thirds { width: 66.666667%; } + +.one-fourth { width: 25%; } + +.one-half { width: 50%; } + +.three-fourths { width: 75%; } + +.one-fifth { width: 20%; } + +.four-fifths { width: 80%; } + +.centered { display: block; float: none; margin-right: auto; margin-left: auto; } + +.col-1 { width: 8.3333333333%; } + +.col-2 { width: 16.6666666667%; } + +.col-3 { width: 25%; } + +.col-4 { width: 33.3333333333%; } + +.col-5 { width: 41.6666666667%; } + +.col-6 { width: 50%; } + +.col-7 { width: 58.3333333333%; } + +.col-8 { width: 66.6666666667%; } + +.col-9 { width: 75%; } + +.col-10 { width: 83.3333333333%; } + +.col-11 { width: 91.6666666667%; } + +.col-12 { width: 100%; } + +@media (min-width: 544px) { .col-sm-1 { width: 8.3333333333%; } + .col-sm-2 { width: 16.6666666667%; } + .col-sm-3 { width: 25%; } + .col-sm-4 { width: 33.3333333333%; } + .col-sm-5 { width: 41.6666666667%; } + .col-sm-6 { width: 50%; } + .col-sm-7 { width: 58.3333333333%; } + .col-sm-8 { width: 66.6666666667%; } + .col-sm-9 { width: 75%; } + .col-sm-10 { width: 83.3333333333%; } + .col-sm-11 { width: 91.6666666667%; } + .col-sm-12 { width: 100%; } } +@media (min-width: 768px) { .col-md-1 { width: 8.3333333333%; } + .col-md-2 { width: 16.6666666667%; } + .col-md-3 { width: 25%; } + .col-md-4 { width: 33.3333333333%; } + .col-md-5 { width: 41.6666666667%; } + .col-md-6 { width: 50%; } + .col-md-7 { width: 58.3333333333%; } + .col-md-8 { width: 66.6666666667%; } + .col-md-9 { width: 75%; } + .col-md-10 { width: 83.3333333333%; } + .col-md-11 { width: 91.6666666667%; } + .col-md-12 { width: 100%; } } +@media (min-width: 1012px) { .col-lg-1 { width: 8.3333333333%; } + .col-lg-2 { width: 16.6666666667%; } + .col-lg-3 { width: 25%; } + .col-lg-4 { width: 33.3333333333%; } + .col-lg-5 { width: 41.6666666667%; } + .col-lg-6 { width: 50%; } + .col-lg-7 { width: 58.3333333333%; } + .col-lg-8 { width: 66.6666666667%; } + .col-lg-9 { width: 75%; } + .col-lg-10 { width: 83.3333333333%; } + .col-lg-11 { width: 91.6666666667%; } + .col-lg-12 { width: 100%; } } +@media (min-width: 1280px) { .col-xl-1 { width: 8.3333333333%; } + .col-xl-2 { width: 16.6666666667%; } + .col-xl-3 { width: 25%; } + .col-xl-4 { width: 33.3333333333%; } + .col-xl-5 { width: 41.6666666667%; } + .col-xl-6 { width: 50%; } + .col-xl-7 { width: 58.3333333333%; } + .col-xl-8 { width: 66.6666666667%; } + .col-xl-9 { width: 75%; } + .col-xl-10 { width: 83.3333333333%; } + .col-xl-11 { width: 91.6666666667%; } + .col-xl-12 { width: 100%; } } +.gutter { margin-right: -16px; margin-left: -16px; } +.gutter > [class*="col-"] { padding-right: 16px !important; padding-left: 16px !important; } + +.gutter-condensed { margin-right: -8px; margin-left: -8px; } +.gutter-condensed > [class*="col-"] { padding-right: 8px !important; padding-left: 8px !important; } + +.gutter-spacious { margin-right: -24px; margin-left: -24px; } +.gutter-spacious > [class*="col-"] { padding-right: 24px !important; padding-left: 24px !important; } + +@media (min-width: 544px) { .gutter-sm { margin-right: -16px; margin-left: -16px; } + .gutter-sm > [class*="col-"] { padding-right: 16px !important; padding-left: 16px !important; } + .gutter-sm-condensed { margin-right: -8px; margin-left: -8px; } + .gutter-sm-condensed > [class*="col-"] { padding-right: 8px !important; padding-left: 8px !important; } + .gutter-sm-spacious { margin-right: -24px; margin-left: -24px; } + .gutter-sm-spacious > [class*="col-"] { padding-right: 24px !important; padding-left: 24px !important; } } +@media (min-width: 768px) { .gutter-md { margin-right: -16px; margin-left: -16px; } + .gutter-md > [class*="col-"] { padding-right: 16px !important; padding-left: 16px !important; } + .gutter-md-condensed { margin-right: -8px; margin-left: -8px; } + .gutter-md-condensed > [class*="col-"] { padding-right: 8px !important; padding-left: 8px !important; } + .gutter-md-spacious { margin-right: -24px; margin-left: -24px; } + .gutter-md-spacious > [class*="col-"] { padding-right: 24px !important; padding-left: 24px !important; } } +@media (min-width: 1012px) { .gutter-lg { margin-right: -16px; margin-left: -16px; } + .gutter-lg > [class*="col-"] { padding-right: 16px !important; padding-left: 16px !important; } + .gutter-lg-condensed { margin-right: -8px; margin-left: -8px; } + .gutter-lg-condensed > [class*="col-"] { padding-right: 8px !important; padding-left: 8px !important; } + .gutter-lg-spacious { margin-right: -24px; margin-left: -24px; } + .gutter-lg-spacious > [class*="col-"] { padding-right: 24px !important; padding-left: 24px !important; } } +@media (min-width: 1280px) { .gutter-xl { margin-right: -16px; margin-left: -16px; } + .gutter-xl > [class*="col-"] { padding-right: 16px !important; padding-left: 16px !important; } + .gutter-xl-condensed { margin-right: -8px; margin-left: -8px; } + .gutter-xl-condensed > [class*="col-"] { padding-right: 8px !important; padding-left: 8px !important; } + .gutter-xl-spacious { margin-right: -24px; margin-left: -24px; } + .gutter-xl-spacious > [class*="col-"] { padding-right: 24px !important; padding-left: 24px !important; } } +.offset-1 { margin-left: 8.3333333333% !important; } + +.offset-2 { margin-left: 16.6666666667% !important; } + +.offset-3 { margin-left: 25% !important; } + +.offset-4 { margin-left: 33.3333333333% !important; } + +.offset-5 { margin-left: 41.6666666667% !important; } + +.offset-6 { margin-left: 50% !important; } + +.offset-7 { margin-left: 58.3333333333% !important; } + +.offset-8 { margin-left: 66.6666666667% !important; } + +.offset-9 { margin-left: 75% !important; } + +.offset-10 { margin-left: 83.3333333333% !important; } + +.offset-11 { margin-left: 91.6666666667% !important; } + +@media (min-width: 544px) { .offset-sm-1 { margin-left: 8.3333333333% !important; } + .offset-sm-2 { margin-left: 16.6666666667% !important; } + .offset-sm-3 { margin-left: 25% !important; } + .offset-sm-4 { margin-left: 33.3333333333% !important; } + .offset-sm-5 { margin-left: 41.6666666667% !important; } + .offset-sm-6 { margin-left: 50% !important; } + .offset-sm-7 { margin-left: 58.3333333333% !important; } + .offset-sm-8 { margin-left: 66.6666666667% !important; } + .offset-sm-9 { margin-left: 75% !important; } + .offset-sm-10 { margin-left: 83.3333333333% !important; } + .offset-sm-11 { margin-left: 91.6666666667% !important; } } +@media (min-width: 768px) { .offset-md-1 { margin-left: 8.3333333333% !important; } + .offset-md-2 { margin-left: 16.6666666667% !important; } + .offset-md-3 { margin-left: 25% !important; } + .offset-md-4 { margin-left: 33.3333333333% !important; } + .offset-md-5 { margin-left: 41.6666666667% !important; } + .offset-md-6 { margin-left: 50% !important; } + .offset-md-7 { margin-left: 58.3333333333% !important; } + .offset-md-8 { margin-left: 66.6666666667% !important; } + .offset-md-9 { margin-left: 75% !important; } + .offset-md-10 { margin-left: 83.3333333333% !important; } + .offset-md-11 { margin-left: 91.6666666667% !important; } } +@media (min-width: 1012px) { .offset-lg-1 { margin-left: 8.3333333333% !important; } + .offset-lg-2 { margin-left: 16.6666666667% !important; } + .offset-lg-3 { margin-left: 25% !important; } + .offset-lg-4 { margin-left: 33.3333333333% !important; } + .offset-lg-5 { margin-left: 41.6666666667% !important; } + .offset-lg-6 { margin-left: 50% !important; } + .offset-lg-7 { margin-left: 58.3333333333% !important; } + .offset-lg-8 { margin-left: 66.6666666667% !important; } + .offset-lg-9 { margin-left: 75% !important; } + .offset-lg-10 { margin-left: 83.3333333333% !important; } + .offset-lg-11 { margin-left: 91.6666666667% !important; } } +@media (min-width: 1280px) { .offset-xl-1 { margin-left: 8.3333333333% !important; } + .offset-xl-2 { margin-left: 16.6666666667% !important; } + .offset-xl-3 { margin-left: 25% !important; } + .offset-xl-4 { margin-left: 33.3333333333% !important; } + .offset-xl-5 { margin-left: 41.6666666667% !important; } + .offset-xl-6 { margin-left: 50% !important; } + .offset-xl-7 { margin-left: 58.3333333333% !important; } + .offset-xl-8 { margin-left: 66.6666666667% !important; } + .offset-xl-9 { margin-left: 75% !important; } + .offset-xl-10 { margin-left: 83.3333333333% !important; } + .offset-xl-11 { margin-left: 91.6666666667% !important; } } +.markdown-body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-size: 16px; line-height: 1.5; word-wrap: break-word; } +.markdown-body::before { display: table; content: ""; } +.markdown-body::after { display: table; clear: both; content: ""; } +.markdown-body > *:first-child { margin-top: 0 !important; } +.markdown-body > *:last-child { margin-bottom: 0 !important; } +.markdown-body a:not([href]) { color: inherit; text-decoration: none; } +.markdown-body .absent { color: #cb2431; } +.markdown-body .anchor { float: left; padding-right: 4px; margin-left: -20px; line-height: 1; } +.markdown-body .anchor:focus { outline: none; } +.markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre { margin-top: 0; margin-bottom: 16px; } +.markdown-body hr { height: 0.25em; padding: 0; margin: 24px 0; background-color: #e1e4e8; border: 0; } +.markdown-body blockquote { padding: 0 1em; color: #6a737d; border-left: 0.25em solid #dfe2e5; } +.markdown-body blockquote > :first-child { margin-top: 0; } +.markdown-body blockquote > :last-child { margin-bottom: 0; } +.markdown-body kbd { display: inline-block; padding: 3px 5px; font-size: 11px; line-height: 10px; color: #444d56; vertical-align: middle; background-color: #fafbfc; border: solid 1px #c6cbd1; border-bottom-color: #959da5; border-radius: 3px; box-shadow: inset 0 -1px 0 #959da5; } + +.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 { margin-top: 24px; margin-bottom: 16px; font-weight: 600; line-height: 1.25; } +.markdown-body h1 .octicon-link, .markdown-body h2 .octicon-link, .markdown-body h3 .octicon-link, .markdown-body h4 .octicon-link, .markdown-body h5 .octicon-link, .markdown-body h6 .octicon-link { color: #1b1f23; vertical-align: middle; visibility: hidden; } +.markdown-body h1:hover .anchor, .markdown-body h2:hover .anchor, .markdown-body h3:hover .anchor, .markdown-body h4:hover .anchor, .markdown-body h5:hover .anchor, .markdown-body h6:hover .anchor { text-decoration: none; } +.markdown-body h1:hover .anchor .octicon-link, .markdown-body h2:hover .anchor .octicon-link, .markdown-body h3:hover .anchor .octicon-link, .markdown-body h4:hover .anchor .octicon-link, .markdown-body h5:hover .anchor .octicon-link, .markdown-body h6:hover .anchor .octicon-link { visibility: visible; } +.markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code { font-size: inherit; } +.markdown-body h1 { padding-bottom: 0.3em; font-size: 2em; border-bottom: 1px solid #eaecef; } +.markdown-body h2 { padding-bottom: 0.3em; font-size: 1.5em; border-bottom: 1px solid #eaecef; } +.markdown-body h3 { font-size: 1.25em; } +.markdown-body h4 { font-size: 1em; } +.markdown-body h5 { font-size: 0.875em; } +.markdown-body h6 { font-size: 0.85em; color: #6a737d; } + +.markdown-body ul, .markdown-body ol { padding-left: 2em; } +.markdown-body ul.no-list, .markdown-body ol.no-list { padding: 0; list-style-type: none; } +.markdown-body ul ul, .markdown-body ul ol, .markdown-body ol ol, .markdown-body ol ul { margin-top: 0; margin-bottom: 0; } +.markdown-body li { word-wrap: break-all; } +.markdown-body li > p { margin-top: 16px; } +.markdown-body li + li { margin-top: 0.25em; } +.markdown-body dl { padding: 0; } +.markdown-body dl dt { padding: 0; margin-top: 16px; font-size: 1em; font-style: italic; font-weight: 600; } +.markdown-body dl dd { padding: 0 16px; margin-bottom: 16px; } + +.markdown-body table { display: block; width: 100%; overflow: auto; } +.markdown-body table th { font-weight: 600; } +.markdown-body table th, .markdown-body table td { padding: 6px 13px; border: 1px solid #dfe2e5; } +.markdown-body table tr { background-color: #fff; border-top: 1px solid #c6cbd1; } +.markdown-body table tr:nth-child(2n) { background-color: #f6f8fa; } +.markdown-body table img { background-color: transparent; } + +.markdown-body img { max-width: 100%; box-sizing: content-box; background-color: #fff; } +.markdown-body img[align=right] { padding-left: 20px; } +.markdown-body img[align=left] { padding-right: 20px; } +.markdown-body .emoji { max-width: none; vertical-align: text-top; background-color: transparent; } +.markdown-body span.frame { display: block; overflow: hidden; } +.markdown-body span.frame > span { display: block; float: left; width: auto; padding: 7px; margin: 13px 0 0; overflow: hidden; border: 1px solid #dfe2e5; } +.markdown-body span.frame span img { display: block; float: left; } +.markdown-body span.frame span span { display: block; padding: 5px 0 0; clear: both; color: #24292e; } +.markdown-body span.align-center { display: block; overflow: hidden; clear: both; } +.markdown-body span.align-center > span { display: block; margin: 13px auto 0; overflow: hidden; text-align: center; } +.markdown-body span.align-center span img { margin: 0 auto; text-align: center; } +.markdown-body span.align-right { display: block; overflow: hidden; clear: both; } +.markdown-body span.align-right > span { display: block; margin: 13px 0 0; overflow: hidden; text-align: right; } +.markdown-body span.align-right span img { margin: 0; text-align: right; } +.markdown-body span.float-left { display: block; float: left; margin-right: 13px; overflow: hidden; } +.markdown-body span.float-left span { margin: 13px 0 0; } +.markdown-body span.float-right { display: block; float: right; margin-left: 13px; overflow: hidden; } +.markdown-body span.float-right > span { display: block; margin: 13px auto 0; overflow: hidden; text-align: right; } + +.markdown-body code, .markdown-body tt { padding: 0.2em 0.4em; margin: 0; font-size: 85%; background-color: rgba(27, 31, 35, 0.05); border-radius: 3px; } +.markdown-body code br, .markdown-body tt br { display: none; } +.markdown-body del code { text-decoration: inherit; } +.markdown-body pre { word-wrap: normal; } +.markdown-body pre > code { padding: 0; margin: 0; font-size: 100%; word-break: normal; white-space: pre; background: transparent; border: 0; } +.markdown-body .highlight { margin-bottom: 16px; } +.markdown-body .highlight pre { margin-bottom: 0; word-break: normal; } +.markdown-body .highlight pre, .markdown-body pre { padding: 16px; overflow: auto; font-size: 85%; line-height: 1.45; background-color: #f6f8fa; border-radius: 3px; } +.markdown-body pre code, .markdown-body pre tt { display: inline; max-width: auto; padding: 0; margin: 0; overflow: visible; line-height: inherit; word-wrap: normal; background-color: transparent; border: 0; } + +.markdown-body .csv-data td, .markdown-body .csv-data th { padding: 5px; overflow: hidden; font-size: 12px; line-height: 1; text-align: left; white-space: nowrap; } +.markdown-body .csv-data .blob-num { padding: 10px 8px 9px; text-align: right; background: #fff; border: 0; } +.markdown-body .csv-data tr { border-top: 0; } +.markdown-body .csv-data th { font-weight: 600; background: #f6f8fa; border-top: 0; } + +.highlight table td { padding: 5px; } + +.highlight table pre { margin: 0; } + +.highlight .cm { color: #999988; font-style: italic; } + +.highlight .cp { color: #999999; font-weight: bold; } + +.highlight .c1 { color: #999988; font-style: italic; } + +.highlight .cs { color: #999999; font-weight: bold; font-style: italic; } + +.highlight .c, .highlight .cd { color: #999988; font-style: italic; } + +.highlight .err { color: #a61717; background-color: #e3d2d2; } + +.highlight .gd { color: #000000; background-color: #ffdddd; } + +.highlight .ge { color: #000000; font-style: italic; } + +.highlight .gr { color: #aa0000; } + +.highlight .gh { color: #999999; } + +.highlight .gi { color: #000000; background-color: #ddffdd; } + +.highlight .go { color: #888888; } + +.highlight .gp { color: #555555; } + +.highlight .gs { font-weight: bold; } + +.highlight .gu { color: #aaaaaa; } + +.highlight .gt { color: #aa0000; } + +.highlight .kc { color: #000000; font-weight: bold; } + +.highlight .kd { color: #000000; font-weight: bold; } + +.highlight .kn { color: #000000; font-weight: bold; } + +.highlight .kp { color: #000000; font-weight: bold; } + +.highlight .kr { color: #000000; font-weight: bold; } + +.highlight .kt { color: #445588; font-weight: bold; } + +.highlight .k, .highlight .kv { color: #000000; font-weight: bold; } + +.highlight .mf { color: #009999; } + +.highlight .mh { color: #009999; } + +.highlight .il { color: #009999; } + +.highlight .mi { color: #009999; } + +.highlight .mo { color: #009999; } + +.highlight .m, .highlight .mb, .highlight .mx { color: #009999; } + +.highlight .sb { color: #d14; } + +.highlight .sc { color: #d14; } + +.highlight .sd { color: #d14; } + +.highlight .s2 { color: #d14; } + +.highlight .se { color: #d14; } + +.highlight .sh { color: #d14; } + +.highlight .si { color: #d14; } + +.highlight .sx { color: #d14; } + +.highlight .sr { color: #009926; } + +.highlight .s1 { color: #d14; } + +.highlight .ss { color: #990073; } + +.highlight .s { color: #d14; } + +.highlight .na { color: #008080; } + +.highlight .bp { color: #999999; } + +.highlight .nb { color: #0086B3; } + +.highlight .nc { color: #445588; font-weight: bold; } + +.highlight .no { color: #008080; } + +.highlight .nd { color: #3c5d5d; font-weight: bold; } + +.highlight .ni { color: #800080; } + +.highlight .ne { color: #990000; font-weight: bold; } + +.highlight .nf { color: #990000; font-weight: bold; } + +.highlight .nl { color: #990000; font-weight: bold; } + +.highlight .nn { color: #555555; } + +.highlight .nt { color: #000080; } + +.highlight .vc { color: #008080; } + +.highlight .vg { color: #008080; } + +.highlight .vi { color: #008080; } + +.highlight .nv { color: #008080; } + +.highlight .ow { color: #000000; font-weight: bold; } + +.highlight .o { color: #000000; font-weight: bold; } + +.highlight .w { color: #bbbbbb; } + +.highlight { background-color: #f8f8f8; } diff --git a/docs/releases/7.0.0/chapters/110-introduction.html b/docs/releases/7.0.0/chapters/110-introduction.html new file mode 100644 index 0000000000..203262da85 --- /dev/null +++ b/docs/releases/7.0.0/chapters/110-introduction.html @@ -0,0 +1,423 @@ + + + +
+ + + + + + + + + +OSGi is arguably one of the best specifications in the Java world. It is a no compromise specification for a component framework that is based on what we today know are the best practices. It extends the type safety first model of Java to hold true over time when the modules morph into sometimes unrecognizable shapes. It provides a solid foundation to build applications that can run anywhere, the original promise of Java; offering these impressive features while remaining as simple as possible, albeit no simpler.
+ +For some, the previous paragraph may come as a surprise because OSGi has had its share of people complaining about it. Surprisingly, the biggest complaint is often the Class Not Found Exception, which is always a perfect sign that people try to push a round peg in a too small square hole, and with all their might. You only see those exceptions when you’re not doing engineering but when you are hacking. If you run head on into the walls that OSGi installs and it is giving you a headache, then just look around and find the elegant and easy to use doors: services.
+ +Though this is all true, I do not claim that OSGi is trivial to use; triviality has a way to clash with large system that must evolve over many years. The software profession has a brutal industry that lures us with the siren song of a ‘few hours work’ to devour us while trying to main gigantic hairballs. As Fred Brooks already said so many years ago in his seminal book ‘The Mythical Man Month’, there is no silver bullet. Even OSGi will require hard work to build evolvable systems. And though we cannot make building complex systems easy, bnd can at least make it easier (and considerably more fun).
+ +When we started building the OSGi specifications around 1998 it became clear we needed metadata to describe the bundles. We chose the manifest since this was already a well defined resource in the JAR, why invent the wheel? Actually, it was Anselm Baird, a Sun employee at the time, who had come up with this idea in his Java Embedded Server (JES). Basically, the manifest was a property file with benefits. We still consider this choice for embedded metadata an excellent idea. Looking at Maven where the metadata is separated over a surprising number of files. It is clear that the reduction in the number of files and potential errors caused by out-of-sync metadata is quite large. My favorite example of this additional complexity is when the SHA1 files necessary in Maven were also signed, an easy mistake but induced by unnecessary complexity. An OSGi bundle is a completely self described artifact.
+ +So who got bnd started? Well, when we wrote our bundles in the dawn of the new millenium we quickly discovered that transient dependencies, well, eh, sucked. Several of us had great experience in Object Oriented design, and though objects clearly had become mainstream they frustratingly had not provided the level of reusability that some had dreamed of. As Brian Foote shows, we thought we were building shiny castles but in the end built a big ball of mud. Where our predecessors of the structured programming era always hammered on low coupling and high cohesion we had ignored their wise lessons and got ourselves tangled up in a nice mess. Trying to reuse a class too often dragged in more unwanted stuff than the cat does in the whole year.
+ +The advantage of Java was that it had found an elegant solution to the scourge of transient dependencies: Java interfaces. An interface elegantly broke transitive dependencies while maintaining type safety at the cost of an indirection. A cost anyone should gladly bear for hairballs are quite distasteful. Surprisingly, while Java offered us this elegant solution to break transitive dependencies, most people in our industry were blindly committing themselves to the same route, just on another level, which actually would make the consequences even worse.
+ +What we discovered was that we could use the idea of interfaces in an object oriented environment also for modules. An interface is an instance of contract based programming and we found out that we could use the same between modules. The idea behind the OSGi service is that you do not let modules directly interact but that you use reified conduits between the modules so that you keep the modules, which are implementations, unconnected. What better concept in Java to express this contract between modules is there but the concept of a package?
+ +Originally devised as the Java modules, Java packages are heavily underappreciated in the Java world. Even highly expert users sometimes only appreciate the namescoping of the package, brushing over any accessibility issues arising out of the use of multiple classloaders. They are wrong, a package is a perfect module. That said, it is a perfect encapsulation to describe the contract that governs the collaboration between modules. An interface has a too small granularity for such a collaboration contract since it often requires additional interfaces like listeners and also helper classes. Packages, although they are too fine grained for deployment modules, are eminently suitable to define a contract to given the collaboration between deployment modules.
+ +So we had come up with the surprisingly well fitting dependency model: ‘Look Ma, No Coupling!’ But then using it became quite painful because it required a lot of tedious house keeping, tracking which packages you were using, of what version. Just bluntly expressing your dependency on an implementation sometimes felt as attractive as the next shot of heroin for a junkie. Now, housekeeping and chores in general are quite low on the list of reasons to give meaning to live for most software developers, and we were no exception. Not only are we in general horrible at chores, we tend to make an amazing amount of mistakes doing them. Which, in general is not good software engineering. Fortunately, we largely live in a virtual world where it is easy to let the computer do the chores. And this is the raison d’etre of bnd: take the chores out of OSGi and focus on the fun parts. And maybe even more important, ‘Do Not Repeat Yourself’.
+ +The early version of bnd was called btool and was incepted at Ericsson, where it was used to automate the generation of the manifest in the ebox project. Then, in 2001, btool was used to automate the build process of the specifications, the reference implementations, and the test suites at the OSGi Alliance. One day there was a bit of confusion about the pedigree of the btool, and it was showing its age; it was time for a rewrite and bnd was born.
+ +The name came from bundle, then removing the vowels, and cutting it to three characters to reduce the typing on the command line. The pronunciation: we actually have no clue, whatever your preference is. B AND D, bind, whatever. You want to write it as bnd, Bnd, or BND, be our guest. We prefer to write it like bnd since we love the symmetry.
+ +Over the years bnd gained a lot of power because we needed a tool at the OSGi Alliance that could not just create good bundles, it also was heavily used in the test cases that required bad bundles. Of course this was an excellent test range for bnd and this synergy is still true today.
+ +In 2003 Eclipse decided to adopt the OSGi specifications and this generated some interesting discussions. To the OSGi crowd it was crystal clear that packages were the future, the Eclipse crowd, coming from a traditional transitive direct hard module dependency world, this future was more fuzzy. We lost, and Require-Bundle and Fragment-Host are the souvenirs of that battle. However, this was not the only disagreement, unfortunately we also quarreled about how to develop bundles (sorry, plugins) and there we lost again with PDE and P2 the landmarks remembering us of this lost fight. Our experience with bnd had taught us that one should not try to maintain the manifest by hand, just like one should not try to write class files with a hex editor. The manifest is a readable format but it was never intended to be human writable except for emergencies. To simplify the runtime (important in an embedded world) we never made the manifest easy to write. For example, when you specify the packages you want to export it is very useful to use wildcards to include a number of sub-packages. However, in runtime this would force the framework to traverse all classes to make the list of total packages of which it could then select the packages based on the wildcards. There is also an inevitable duplication of information as well as derived information which makes it impossible to not create evil redundancy.
+ +Unfortunately, the PDE guys insisted on ‘manifest first’. In their model, the manifest drives the building of the artifact instead of representing the output of a build process. That is, if you require a bundle in your manifest, then PDE will place this on the classpath of the bundle you’re building. If you import a package in the manifest, PDE will find a bundle exporting that package and place it on the classpath (praying there is only one bundle that exports it in the target).
+ +Inside the OSGi Alliance we were in a bind (pun intended) because PDE was not suitable for our own build. We used bnd in ant (it was also an ant plugin) but we missed the joy of Eclipse. This caused bnd to develop a split personality. Originally it was a JAR generator based on a small recipe but for our build we needed project & workspace concepts. We toyed with the idea to split it into a bld and a bnd tool but in the end even we committed the sin against modularity of low cohesion and kept these two tools in one out of laziness.
+ +Obviously we also developed PDE envy because Eclipse was actually awfully nice except for it. After toying with the idea of using the Eclipse metadata (.classpath) and finding out that this was only possible if we included the complete Eclipse IDE in an offline build, we embarked on developing an Eclipse plugin. Just not a good idea. So we reversed the model, and developed a library that had its own internal, uncoupled, model of a build. You see, the problem is that most build environments are quite pedantic and strongly optimized for their primary goal. Ant, maven, gradle, et. al. are just not easy to use inside an IDE because they are very stream driven: start, process, stop. In this model there is very little incentive to optimize incremental building and event notifications for important changes. An IDE is the reverse, it is start, build, build, build, …, build, build, stop. For performance, it is crucial to optimize the building out of incremental changes to keep the IDE responsive. It is also crucial to send out events when important things happen. So we decided to pursue the middle ground: a model of projects and workspaces that was as uncoupled of the real world as possible but providing the hooks to use it in all popular build tools, either command line tools or IDEs. This is rather well captured in the expression: “The one tool that bnd’s them all”.
+ +Then one day we heard that Neil Bartlett also had started a plugin called bndtools. He had used the open source library of bnd and started to work on creating a pleasant to use friendly environment. Interestingly, he had created a continuous builder for bnd so that every save operation automatically build the bundles, something we had considered in our plugin but had been too afraid to do out of performance fear. Neil, however, was developing a lot of stuff that was already in bnd, he only used the JAR packager and manifest generator. After beating him up, which even took a special trip to the UK where we worked for 8 hours in a hotel lounge, he surrendered and thus our long term fruitful collaboration was born.
+ +Currently bnd(tools) is managed through a Github organization called bndtools and has its home site at https://bndtools.org. It is split in a number of repositories: bndtools, bnd, bndtools.rt and a number of associated support repositories. Though the collaboration between bndtools and bnd is awfully close, we are fanatic about separating the projects, our goal is still to allow Jetbrains to use bnd in IntelliJ without forcing them to eat any Eclipsisms. And it is always fun when hear someone claiming to successfully integrate bndlib in a product or tool, raving how uncoupled it was.
+ +What you are now reading is the manual of bnd, which is the result of a tremendous amount of work, and will be a work in progress for a long time to be. Though much of bnd’s development was indirectly supported by the OSGi Alliance it is and always will be an open source project. This gave the authors the license to scratch their itches and not worry too much about documenting the nifty things they developed to scratch. Though the most important aspects were documented, it was sparse and not overly well organized. Though we always hoped someone with a gift for documenting would come along, fall in love with bnd, and write the perfect documentation, this somehow failed. After 16 years, we find it is time to take up this task ourselves, still praying that we will get support from bnd’s surprisingly large (to some) group of users, don’t hesitate. This is the reason this manual is a github repositories. Contributing is trivial, clone the bndtools/bnd.manual repo, edit the markdown text (you can even do this on the github web), save it, and create a pull request. Don’t (always) ask what bnd can do for you, ask what you can do for bnd’s users …
+ +Not sure why we wrote such a long introduction, the facebook generation seems have an attention span of 5 lines, so we are probably among ourselves dear reader, congratulations on your stamina!
+ +bnd is not a single product, it is a library (bndlib) used in many different software build environments. It runs inside Maven, ant, gradle, Eclipse, sbt, and maybe one day in Intellij. To install bnd, you will have to install these tools.
+ +That said, there is also a command line version of bnd, providing an easy way to try out its many features. You can install bnd through brew on MacOS.
+ +You can also run bnd command as executable jar, which can be downloaded from JFrog:
+java -jar biz.aQute.bnd-{VERSION}.jar <command>
+
The binaries are available on JFrog. The latest version can be found at:
+ +https://bndtools.jfrog.io/bndtools/libs-snapshot
+
If you are a maven user, you can find many version in central. The coordinates are:
+ + <dependency>
+ <groupId>biz.aQute.bnd</groupId>
+ <artifactId>biz.aQute.bndlib</artifactId>
+ <version>....</version>
+ </dependency>
+
bnd is maintained at github. If you want to change the code, just clone it and modify it. In general we accept pull requests, and often even highly appreciate them.
+ +The manual is also on github. If you see an improvement, do not hesitate to clone the repo and create a pull request. Improvements are bug corrections but we also like short articles about how to do do something with bnd.
+ +If you’re behind a firewall that requires proxies or you use repositories that require authentication see -connection-settings.
+ + +The purpose of this com.acme.prime is to show build administrators how to setup workspaces and what features bndlib provides to automate common tasks. This section is not intended to be used by people wanting to learn OSGi, please use a bndtools tutorial for this.
+ +This section will go through the process of creating a workspace and a few projects while explaining what functions are useful in each phase. It will remain at a rather high level to keep the text flowing, details can be found in the different reference sections.
+ +Since this section provides a tool independent view of bndlib, we use the bnd command line application to demonstrate the features. Though this is an excellent way to show the low level functionality (the porcelain in git terms), it is not the normal way bndlib is used. In general, a build tool like gradle, ant, make, or maven drives this process from the command line an IDE like bndtools handles the user interaction. So please, please, do not take this com.acme.prime as a guide how to create a build. However handy bnd is, it falls far short of a real build tool like for example gradle.
+ +A workspace is a directory with the following mandatory files/directories:
+ +./
+ cnf/
+ build.bnd
+ ext/
+
That’s all! So let’s create one:
+ +$ bnd add workspace com.acme.prime
+$ cd com.acme.prime
+$ ls
+cnf
+
The cnf
directory is the ‘magic’ directory that makes a directory a workspace, just like the .git
directory does for git.
In the cnf
directory you find the following files:
$ ls cnf
+bnd.bnd build.bnd ext
+
The build.bnd
is part of the magic to make something a workspace, it contains your workspace properties. The ext
directory contains more properties. The bnd.bnd
is the last piece of magic, it makes bndlib recognize the cnf
directory as a project.
Why such a long name for the workspace? Wouldn’t just tour
suffice? Well, glad you asked. If you work with bnd(tools) for some time you will find that you will get many different workspaces since a workspace is another level of modularity. You can see a workspace as a cohesive set of bundles; just like any module it can import and export. Just like any other module, it imports and exports the thing it encapsulates. For example, a bundle imports and exports packages. In case of the workspace this is the bundle. A workspace imports and exports bundles through repositories.
A good module has cohesion; this means that its constituents have a rather strong relation. Since they also tend to come from the same organization they will have very similar bundle symbolic names. Since some of these bundles will escape in the wild it is always beneficial if you can quickly find the source of that bundle. Therefore naming the workspace with the shared prefix of the bundle symbolic names of its constituents is highly recommended.
+ +That said, bndlib does not enforce this guideline in any way, unlike project names. You can name your workspace foo
if you want to.
A workspace so created is quite empty. However, if we look in the cnf
directory then we can see the build.bnd
file. This file is 100% for you … Any property, instruction, or header specified in here is available in anything you build in this workspace; all other bnd files will inherit everything from this properties file. In this file should add for example the headers Bundle-Vendor and Bundle-Copyright. However, using the macro language we can also add custom variables and macros that are useful across the workspace.
An important aspect of the workspace is that it hosts plugins. A plugin is an extension to bndlib that gets loaded when the workspace is opened. Plugins provide a lot of different functions in bndlib. You can see the currently loaded plugins with bnd:
+ +$ bnd plugins
+000 Workspace [com.acme.prime]
+001 java.util.concurrent.ThreadPoolExecutor@a4102b8[Running, ...]
+002 java.util.Random@11dc3715
+003 Maven [m2=...]
+004 Settings[/Users/aqute/.bnd/settings.json]
+005 bnd-cache
+006 ResourceRepositoryImpl [... ]
+007 aQute.bnd.osgi.Processor
+
The plugins you see are the built-in plugins of bnd itself, they always are available. However, the purpose of plugins is to extend the base functionality. As almost everything, the set of external plugins is managed through an instruction, which is a property in the Workspace.
+ +Before bndlib reads the build.bnd
file to read the workspace properties, it first reads all the files with a .bnd
extension in the cnf/ext
folder. The purpose of this folder is to manage setups for plugins. We can add additional plugins by their name. You can see a list of built-in plugins with the add plugin command:
$ bnd add plugin
+Type Description
+ant aQute.bnd.plugin.ant.AntPlugin
+blueprint aQute.lib.spring.SpringXMLType
+eclipse aQute.bnd.plugin.eclipse.EclipsePlugin
+filerepo aQute.lib.deployer.FileRepo
+git aQute.bnd.plugin.git.GitPlugin
+gradle aQute.bnd.plugin.gradle.GradlePlugin
+...
+
An interesting plugin is the Eclipse plugin that will prepare any projects for Eclipse. We could also add the git plugin that will make sure the proper .gitignore files are in place.
+ +$ bnd add plugin eclipse
+$ bnd add plugin git
+$ bnd plugins
+...
+007 aQute.bnd.osgi.Processor
+008 EclipsePlugin
+009 GitPlugin
+
Since you likely need to maintain this build it is good to know how this is stored. If you look in the cnf/ext
directory you should see an eclipse.bnd
and a git.bnd
file:
$ ls cnf/ext
+eclipse.bnd git.bnd
+
The eclipse.bnd
file contains the following:
$ more cnf/ext/eclipse.bnd
+#
+# Plugin eclipse setup
+#
+-plugin.eclipse = aQute.bnd.plugin.eclipse.EclipsePlugin
+
So how does this work? When the workspace is opened bndlib will first read all the bnd files in the cnf/ext
directory in alphabetical order. After that, it will read the build.bnd
file. The idea of the cnf/ext
files is that they should not be touched by you, the build.bnd
file is, however, all yours. You can override any previous setting in the build.bnd
file since it is read last.
As you can see, the eclipse.bnd
file defines the property -plugin.eclipse
. In most cases that a value should be settable in different places, bndlib uses merged properties. When bndlib loads the plugins, it actually gets the property -plugin
, merged with any other property that has a key that starts with -plugin.
(ordered alphabetically). This allows you to add to a merged property anywhere in the many places in bndlib where you can set properties.
So now we can try to build the workspace:
+ +$ bnd build
+No Projects
+
Which makes some sense …
+ +An empty workspace is not so useful, let’s add a project.
+ +$ bnd add project com.acme.prime.hello
+$ ls -a com.acme.prime.hello/
+. .gitignore bin_test src
+.. .project bnd.bnd test
+.classpath bin generated
+
This classic layout defines separate source folders for the main code and the test code. The generated
directory is a temporary directory, it contains the artifacts produced by this build.
This is a classical Eclipse layout, with a separate src
and test
folder. However, this is not baked into bndlib, it is possible to, for example, use the maven layout with the src/main/java
, src/test/java
, and target
directories. We can try this out with the maven plugin.
$ bnd add plugin maven
+$ more cnf/ext/maven.bnd
+-plugin.maven = aQute.bnd.plugin.maven.MavenPlugin
+
+-outputmask = ${@bsn}-${versionmask;===S;${@version}}.jar
+
+src=src/main/java
+bin=target/classes
+testsrc=src/test/java
+testbin=target/test-classes
+target-dir=target
+
The maven plugin adds a number of properties that are recognized by bndlib and used appropriately.
+ + +Since bnd is by design headless, the best way to get start is to use one of the IDEs like bndtools. They have tutorials and an IDE is a more pleasant place than a command line. So if you just want to learn OSGi, please go away, this chapter is not for you! bndlib relates to OSGi like the ASM byte code manipulation library relates to Java. Sometimes incredibly useful but in general something you do not want to touch, and obviously not the way to learn Java.
+ +Assuming we’re now only left with the blue collar workers of our industry: people that need to maintain builds or that must do JAR engineering. First a word of warning, the fact that bndlib provides a function is in no way an advertisement to use that function. bndlib grew up together with the OSGi specifications and has been used to build the Reference Implementations (RI) and Test Compatibility Kits (TCK). Though this has a lot of benefits for you, the disadvantage is that it also has to support all the bad parts of the specifications, and even sometimes must be able to create erroneous situations so we could create test cases. And we also had to handle the situations caused by the mess of non-modular bundles out there.
+ +So to make it crystal clear: the fact that a function is in bndlib does not mean it is intended to be used. This section contains a whole bunch of things you wish you never had to touch, and if you do OSGi properly, you will only see a tiny fraction of bndlib. That said, when the unprepared JARs hit the OSGi framework, it is nice to have bndlib as backup.
+ +This tour uses the bnd command line bnd to demonstrate much of the inner details of bndlib.
+ +bnd became the Swiss army knife to manipulate JARs and gained a lot of function you should never attempt to use. However, this chapter will give you an overview of what is in bnd.
+ +bnd has lots of commands to try out aspects of bndlib. The install section shows how to install bnd.
+ +A common use of bnd is to wrap a JAR file; there are still too many files out there that have no OSGi metadata.This lack of metadata makes them kind of useless as a bundle since it will be fully isolated from the other bundles. In OSGi, you must explicitly indicate what packages should be imported and what packages should be exported, by default everything is private. Though this can be quite frustrating sometimes, a rather strong argument can be made for the long term benefits of this strategy.
+ +The OSGi metadata is stored in the manifest of the JAR file. The manifest is a like a properties file stored in META-INF/MANIFEST.MF
in the JAR file. The manifest format is specified by Sun/Oracle. Though the manifest is a text file and human readable it has a number of quirks that makes it hard to edit for humans. So even though it is text, the intention of the manifest was never to be done by hand, bnd like tools were always the intention. Tools can provide many useful defaults, handle expansions, and verify consistency,
So let’s use bnd to wrap the javax.activation.jar file. Let’s download it:
+ +$ mkdir jar
+$ bnd copy https://repo.maven.apache.org/maven2/javax/activation/activation/1.1.1/activation-1.1.1.jar \
+ jar/javax.activation-1.1.1.jar
+
bnd provides a convenience function to print out the contents of a JAR. The –uses option shows the packages and what other packages they use.
+ +$ bnd print --uses jar/javax.activation-1.1.1.jar
+[USES]
+com.sun.activation.registries []
+com.sun.activation.viewers [javax.activation]
+javax.activation [com.sun.activation.registries]
+
In the simplistic case our goal is to provide a manifest that makes all the packages in the source JAR available to the other bundles (exports) and that imports anything the classes in the JAR require from the external world (imports). bnd is controlled by a bnd file. This file is a standard Java properties file. In this properties file we can provide headers, instructions, and macros to bnd. In general, a header starts with an upper case, and an instruction with a minus sign (‘-‘). The format of the headers and instructions is defined by the OSGi specifications. The headers specifically try to follow the structure of an OSGi header. Since exports are defined with the Export-Package
header and imports are defined by the Import-Package
header we can create a bnd file that looks very much like the desired manifest, except for the instruction that defines our class path. We could also use the URL directly on the classpath but this would download the JAR everytime we ran bnd. So javax.activation.bnd
should look like:
-classpath: jar/javax.activation-1.1.1.jar
+Export-Package: *
+
The casing of the headers is important, even though OSGi headers are case insensitive. That is Export-Package is not the same for bnd as Export-package.
+ +The properties format we use in bnd is darned flexibile. A header is recognized when you start the line with a header name (a word without spaces) and then:
+ +Though lines can be as long as you want, it is better for your brains to keep them short. You can break a line with a backslash (‘\’) followed by a newline (‘\n’). Be careful, the newline must follow the backslash or you will get whatever comes after the backslash in the header. Some examples that all set the variable foo
to 3:
foo = 3
+foo:3
+foo 3
+foo \
+3
+
Since bnd files are property files, you cannot repeat a property. Later properties will completely overwrite earlier ones and there is no order between properties.
+ +We can now wrap the source JAR with the following command:
+ +$ bnd javax.activation.bnd
+
bnd has many commands, you can find out more about them with thebnd help [sub]
command. In this case we rely on bnd detecting a bnd file and doing the right thing. bnd is also prepared to show the manifest of any bundle, so let’s take a look at the bundle we just generated:
$ bnd javax.activation.jar
+[MANIFEST javax.activation]
+Bnd-LastModified 1407229057278
+Bundle-ManifestVersion 2
+Bundle-Name javax.activation
+Bundle-SymbolicName javax.activation
+Bundle-Version 0
+Created-By 1.8.0 (Oracle Corporation)
+Export-Package com.sun.activation.registries;version="0.0.0",
+ com.sun.activation.viewers;uses:="javax.activation";version="0.0.0",
+ javax.activation;version="0.0.0"
+Manifest-Version 1.0
+Require-Capability osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.4))"
+Tool Bnd-2.4.0.201408050759
+
When we create the JAR, we can also specify the output file. By default, bnd will store JAR under the same name as the bnd file, but then with a .jar
extension. You can override the default from the command line:
$ mkdir bundle
+$ bnd do --output bundle javax.activation.bnd
+$ ls bundle
+javax.activation.jar
+
You can either specify a directory or a file name. However, you can also specify the output in the bnd file itself:
+ +-output: bundle
+
It is clear that bnd has been busy. It detected 3 packages (com.sun.activation.registries, com.sun.activation.viewers, and javax.activation) and since we exported all, they appear in the manifest as exports. Observant readers will have noticed that we used wildcards (‘*’). The OSGi specifications are not so generous, every package must be explicitly listed so that the manifest contains all the information required to process a bundle. This, again, has many long term macro advantages. It’s however bnd’s goal in life to provide the convenience in this model and that is the reason that bnd supports wildcards in virtual any situation where they are usable. A wildcard requires a domain to be effective, you can only select something out of a set, its domain. In the case of Export-Package
, its domain is the class path. This class path is set by the -classpath
instruction. In this case the activation jar.
Currently our package is exported at version 0.0.0. This is not a very useful version, we therefore need to version the exported packages. Why? Well, this is the contract of modularity. To get the benefits of privacy (the privilege to change whenever we feel the need) comes the cost of compatibility for the parts we share. If you export a package, you intend it to be used by others. These others are an uncontrollable bunch that will use your revision (the actual artifact with a given digest, i.e. the instance of the program) but will expect updates in the future with new functionality and bug fixes.
+ +Since a software program is an ephemeral product that changes over time these others do not become dependent on the revision, nope, they become dependent on the program. This creates two problems.
+ +The answer to these two questions is given to us by semantic versioning, a prescriptive syntax for versions. Though semantic versions work for bundles, they really shine for packages. The basic idea is that when you compile against a version, this version is recorded in the OSGi metadata as an range on the imported package. The floor of this range is the version in the revision use to compile against, the ceiling indicates the first version that would not be compatible anymore. For example, [1.2,2)
indicates that any version that is larger or equal than 1.2 and less than 2 is compatible. Therefore, 1.5 would be compatible. Since OSGi supports version ranges on the Import-Package header it will be able to control what revisions get linked and how.
Such a version scheme of course only works if you agree how to use the version syntax, this should explain the ‘semantic’ in semantic versioning. Therefore, the bnd/OSGi versions have a strictly defined syntax:
+ +Long story, but it is at the heart of what we’re trying to achieve: evolving large systems in a anti-fragile way.
+ +Back to the exported packages. We can add the version to the export-package by decorating the Export-Package header:
+ +-classpath: jar/javax.activation-1.1.1.jar
+Export-Package: *;version=1.1.1
+
Let’s take another look:
+ +$ bnd javax.activation.bnd
+$ bnd javax.activation.jar
+[MANIFEST javax.activation]
+...
+Export-Package com.sun.activation.registries;version="1.1.1",
+ com.sun.activation.viewers;uses:="javax.activation";version="1.1.1",
+ javax.activation;version="1.1.1"
+...
+
Looking at the manifest we can see the uses directive; this directive is the primary reason you should use bnd. To prevent wiring the wrong bundles together, the OSGi Framework must know which packages use what other packages in their API, this list is reflected in the uses constraints. It allows the OSGi Framework to handle multiple versions of the same package in one VM and not create Class Cast Exceptions … Very powerful but it is impossible to maintain this list by hand and it would be prohibitively expensive in runtime.
+ +An exported package is a commitment to any future users out there. This commitment will restrict your freedom to make changes since users can get pretty nasty if you make something that will not work with their existing software. In contrast, a package that is not exported is private; since no external user can have knowledge about this package we have complete freedom to do whatever we want in a future revision without getting hollering users on the phone.
+ +In our example javax.activation JAR we should therefore not export the com.sun.*
packages since they are actually private. The Private-Package header has the same syntax as the Export-Package header but will not export the packages, it will keep them hidden in the bundle. Unlike the Export-Package header, it is not necessary to decorate the packages with a version since nobody can depend on them.
-classpath: jar/javax.activation-1.1.1.jar
+Export-Package: javax.activation;version=1.1.1
+Private-Package: com.sun.activation.*
+
+$ bnd javax.activation.bnd
+$ bnd javax.activation.jar
+[MANIFEST javax.activation]
+...
+Export-Package javax.activation;version="1.1.1"
+Private-Package com.sun.activation.registries,
+ com.sun.activation.viewers
+...
+
The Private-Package has no meaning to OSGi, if you want to not have this header in your manifest then you can also use -private-package
instead.
The Bundle-Version
header obviously provides the version. Default it will use version 0, so we should override it. We could just add the 1.1.1 version from our source JAR, however, this would make it impossible to to distinguish the different variations we might make over time. A good tool is to use the ${tstamp} macro for the qualifier. The qualifier is the last part of the version. A macro provides a textual replacement, bnd has hundreds of macros (including access to Javascript) and you can define your own. The ${tstamp} macro provides a time stamp.
-classpath: jar/javax.activation-1.1.1.jar
+Export-Package: javax.activation;version=1.1.1
+Private-Package: com.sun.activation.*
+Bundle-Version: 1.1.1.${tstamp}
+
The ‘Do Not Repeat Yourself’ mantra encodes one of the more important lessons of software engineering. You should always strive to define a ‘thing’ or a piece of knowledge in only one place. In our little example, we actually repeat ourselves with the version 1.1.1:
+ +bnd has a macro processor on board that is a life saver if you are addicted to DNRY (we are). This macro processor has access to all properties in the bnd file, including headers and instructions. Therefore ${Bundle-Version}
refers to whatever value the Bundle Version is set to. However, in this case the Bundle Version also contains the time stamp in the qualifier, which we do not need in the other places. That is, we do not want the output file name to contain the qualifier.
bnd also contains a large number of built-in macros that provide common utilities. The ${versionmask;mask;version}
is for example such a utility for picking out the parts of a version, bumping it, as well as normalizing it. Normalizing (making sure all parts are present, leading zeros removed, etc.) is crucial to keep things workable.
We can try out the macro from the command line, the bnd command has a macro
sub command:
$ bnd macro "version;===;1.2.3.q" => 1.2.3
+$ bnd macro "version;+00;1.2.3.q" => 2.0.0
+$ bnd macro "version;+00;0" => 1.0.0
+$ bnd macro "version;=;1.2.3.q" => 1
+
To reuse the Bundle Version in the class path, the output, and the export package:
+ +-classpath: jar/javax.activation-${versionmask;===;${Bundle-Version}}.jar
+
+Export-Package: javax.activation;version=${versionmask;===;${Bundle-Version}}
+Private-Package: com.sun.activation.*
+Bundle-Version: 1.1.1.${tstamp}
+
This of course looks awkward and hardly DNRY. A better solution is to create an intermediate variable. Variables are always lower case (if they started with an upper case, they would end up in the manifest). Variables can be referred to with the macro syntax of ${}
.
v: 1.1.1
+
+-classpath: jar/javax.activation-${v}.jar
+-output: bundle
+
+Bundle-Version: ${v}.${tstamp}
+Export-Package: javax.activation;version=${v}
+Private-Package: com.sun.activation.*
+
Maven Central is quickly moving towards a million revisions organized in 200.000 programs. It should be clear that we need to organize this huge pile of software. One way is to make sure you document your programs appropriately. It is highly recommended to have a short one paragraph description in each bundle. Adding a description to our bundle is easy:
+ +v: 1.1.1
+
+-classpath: jar/javax.activation-${v}.jar
+-output: bundle
+
+Bundle-Description: \
+ A wrapped version of javax.activation ${v} JAR from Oracle. \
+ This bundle is downloaded from maven and wrapped by bnd.
+Bundle-Version: ${v}.${tstamp}
+Export-Package: javax.activation;version=${v}
+Private-Package: com.sun.activation.*
+
For longer descriptions you can continue on the next line with a backslash (‘') followed by a newline. Unlike the manifest, it does not has to start with a space though indenting the text for the next lines is usually a good practice. You cannot use newlines or other markup in the Bundle-Description.
+ +That said, a good Bundle-Description is a single paragraph with a few short sentences.
+ +The Bundle-Description is an excellent way to provide a short description, but what about a longer description with examples of usage, caveats, configuration requirements, etc? It would be nice to include a readme
file in the bundle that indexing sites could use. You can achieve this effect by including a readme.md
file in the root of your bundle.
In bnd it is possible to include any resource from anywhere in the file system (actually, any URL as well).
+ +-includeresource: readme.md
+
The resource is of course not always in the proper place, it is therefore possible to make the output name different from the file path:
+ +-includeresource: readme.md=doc/readme.md
+
In this case, the readme could benefit from the bnd macro support. For example, it could then contain the actual version of the artifact or use any of the other macros available. Preprocessing can be indicated by enclosing the clause with curly braces (‘{‘ and ‘}’).
+ +-includeresource: {readme.md=doc/readme.md}
+
Another option is to encode the text for the readme in the bnd file using the literal option (though this is not such a good idea for any decent readme file since it is hard to use newlines):
+ +-includeresource: readme.md;literal=${unescape;#JAF\nThis is the Java Activation Framework}
+
The -includeresource
instruction is quite powerful, there are many more options to recurse directories, filter, etc. See -includeresource.
So far, we’ve ignored the imported packages because the javax.activation JAR only depends on java.; java. packages are not imported, the OSGi Framework will always provide access to them. Let’s add another JAR, the javax.mail jar that uses javax.activation. This JAR is already a bundle, which is really good. Except for this exercise, so we copy and strip the OSGi metadata
+ +$ bnd copy --strip https://repo.maven.apache.org/maven2/com/sun/mail/javax.mail/1.5.2/javax.mail-1.5.2.jar \
+ > jar/javax.mail-1.5.2.jar
+$ bnd print --uses jar/javax.mail-1.5.2.jar
+[USES]
+com.sun.mail.auth [javax.crypto, ... ]
+com.sun.mail.handlers [javax.activation, ... ]
+com.sun.mail.iap [com.sun.mail.util, ... ]
+com.sun.mail.imap [com.sun.mail.imap.protocol, ...]
+com.sun.mail.imap.protocol [com.sun.mail.iap, ... ]
+com.sun.mail.pop3 [javax.mail, ... ]
+com.sun.mail.smtp [com.sun.mail.util, ... ]
+com.sun.mail.util [javax.mail, ... ]
+com.sun.mail.util.logging [javax.mail, ...]
+javax.mail [javax.mail.event, ... ]
+javax.mail.event [javax.mail]
+javax.mail.internet [javax.mail, ... ]
+javax.mail.search [javax.mail.internet, javax.mail]
+javax.mail.util [javax.mail.internet, javax.activation]
+
The javax.mail bundle leverages the Java Activation Framework (JAF) embedded in our previous javax.activation bundle. One of the most useful features in bnd is that it can analyze the class files to find the package dependencies and turn them into an Import-Package header. If bnd finds the exported packages with a version on the class path then it will automatically use the exported versions to calculate the import version range. That is, if the package is exported as 1.1.1, then bnd will import it in general as [1.1,2)
.
So lets wrap the javax.mail package, add to javax.mail.bnd
:
v: 1.5.2
+
+-classpath: \
+ jar/javax.activation-1.1.1.jar, \
+ jar/javax.mail-${v}.jar
+
+Bundle-Version: ${v}
+Bundle-Description: \
+ An OSGi wrapped version of the javax.mail library downloaded from maven.
+Export-Package: javax.mail.*
+Private-Package: com.sun.mail.*
+
bnd contains a special -i/--impexp
option to print the imports and exports of a bundle. So lets make the bundle and see:
$ bnd javax.mail.bnd
+$ bnd print -i javax.mail.jar
+[IMPEXP]
+Import-Package
+ javax.activation
+ javax.crypto
+ javax.crypto.spec
+ javax.mail.event {version=[1.5,2)}
+ javax.mail.search {version=[1.5,2)}
+ javax.mail.util {version=[1.5,2)}
+ javax.net
+ javax.net.ssl
+ javax.security.auth.callback
+ javax.security.auth.x500
+ javax.security.sasl
+ javax.xml.transform
+ javax.xml.transform.stream
+Export-Package
+ javax.mail {version=1.5}
+ javax.mail.event {version=1.5, imported-as=[1.5,2)}
+ javax.mail.internet {version=1.5}
+ javax.mail.search {version=1.5, imported-as=[1.5,2)}
+ javax.mail.util {version=1.5, imported-as=[1.5,2)}
+
First, we notice that there are quite a few imports that have no import range. This is not good but unfortunately Java’s VM package versions are more or less absent and are clearly not semantically versioned. A correctly setup system ensures that the correct execution environment is used.
+ +However, we also have in this list javax.activation, for this package we do have an export, the problem is that we included the original JAR on the class path and not the bundle we generated.
+ +v: 1.5.2
+
+-classpath: \
+ bundle/javax.activation.jar, \
+ jar/javax.mail-${v}.jar
+
+Bundle-Version: ${v}
+Bundle-Description: \
+ An OSGi wrapped version of the javax.mail library downloaded from maven.
+Export-Package: javax.mail.*
+Private-Package: com.sun.mail.*
+
And now build + print:
+ +$ bnd javax.mail.bnd; bnd print -i javax.mail.jar
+[IMPEXP]
+Import-Package
+ javax.activation {version=[1.1,2)}
+ javax.crypto
+ ...
+
Yes! Now the javax.activation package is imported with a range of [1.1,2)
. You now may wonder why not [1.1.1,2)
? Well, the reason is that changes in the micro part of the version should not make a difference in API. If you include the micro part in the import range then it turns out that the overall system becomes very volatile, small changes become large quickly. Ignoring the micro part in the import range is like a bit of oil in the engine … However, if you feel uncomfortable with this lubricant then it is possible to override this.
We got the Import-Package header in the manifest but we never specified it. The reason is that bnd has a default Import-Package set to ‘*’. There is a very good reason for this, it works almost exactly as it should work. Any time you feel the desire to muck around with the imports you should realize that you’re not doing the right thing. That said, it is the unfortunate truth that sometimes doing the least worst thing is the best option …
+ +Assume we want to provide a version range on some of the imported packages because these packages could also be provided by bundles, they do not have to come from the JVM. For this, we can add the following header that will decorate the javax.net.ssl package:
+ +Import-Package: javax.net.*;version=1.1, *
+
You can decorate any package, including packages specified with a wildcard. The domain of the Import-Package is all packaged that are referred to by any class inside the bundle. If there are for example imports
+ +The removeheaders
header uses a selector expression. A selector expression consists of a number of comma separated clauses. A clause contains a name (which can be wildcarded) and some optional instructions. A selector is always applied to a domain, in the case of -removeheaders
the domain is all the headers in the manifest. The clauses in the selector are applied one by one in their declaration order to the domain. If a clause matches a member of the domain then the member is removed from the set, and the next member is again matched against the first clause. A suffix of ‘:i’ indicates a case insensitive match.
This ordering is important because selector clauses can use negation. When a clause starts with an exclamation mark (‘!’) then a matching member is further ignored. Therefore, the following selector will never match the Bundle-Name, the first clause already threw any header that starts with Bundle-
out:
-removeheaders: !Bundle-*, Bundle-Name:i
+
The Require-Capability
header ensures that the runtime has at least a Java 1.4 VM by requiring an OSGi execution environment. bnd calculates this requirement based on the compiler version used to compile the classes in the JAR. We can override this requirement or disable it with the -noee=true
instruction.
We now have 2 files: javax.activation.bnd and javax.mail.bnd. If we would like to add some more descriptive headers like Bundle-Vendor, Bundle-DocURL, and Bundle-Copyright then we would have to add them to both files, another case of DNRY. Since this is quite common, we can use includes.
+ +-include: common.bnd
+
In common.bnd we can then place:
+ +Bundle-Vendor: Oracle (wrapped by bnd)
+Bundle-Copyright: (c) Oracle, All Rights Reserved
+
These headers are then automatically placed in the manifests of our target bundles.
+ +A common mistake with the -include
instruction is that it is repeated several times. Since bnd files are property files, only one of them survives. However, the -include
instruction accepts multiple files/URLs to both property files or manifest files. You can also indicate that it is ok to not have a certain file by prefixing it with a minus sign (‘-‘).
-include common.bnd, -other.bnd
+
Always use Unix like paths, using the forward slash (‘/’) as file separator, also on Windows. It is highly recommended to always use relative paths since any absolute references makes your builds unportable.
+ +In our progression towards wrapping javax.activation and javax.mail we have created an unmanaged ordering; we can only build javax.mail once the javax.activation is created. For this reason, bnd can also create a control file that steers sub bnd files. The control file can also contain common properties, though these can be overridden by the sub files. Place the following content in javax.bnd:
+ +Bundle-Vendor: Oracle (wrapped by bnd)
+Bundle-Copyright: (c) Oracle, All Rights Reserved
+-output: bundle
+-sub: \
+ javax.activation.bnd, \
+ javax.mail.bnd
+
We can now build the jars with a simple command:
+ +$ rm bundle/*
+$ bnd javax.bnd
+$ ls bundle
+javax.activation.jar javax.mail.jar
+
The basic model of bnd is to collect packages from the classpath and assemble them in a bundle. This pull model is quite different from the more common push model in builds where it is harder to include packages from other projects. However, bnd’s model makes it quite easy to create a bundle out of multiple JARs. So lets add a new bnd file that merges javax activation and mail.
+ +Actually, javax.activation and javax.mail are really bad citizens in an OSGi world. They use class loading hacks all over the place that make a mockery out of modularity. Part of the problem is that they really require certain private directories to be available from the client’s class loader. In those cases it is sometimes necessary to make sure the layout of the bundles is exactly the same as the source bundles.
+ +The best way to achieve this is to unroll the source bundles in the target bundle. You can unroll a JAR by prefixing it with a commercial at sign (‘@’) in an include resource operation. Lets get started on a javax.mail.all.bnd file:
+ +-includeresource: \
+ @jar/javax.activation.jar, \
+ @jar/javax.mail.jar
+
This instruction combines the two JARs into one.
+ +bnd is the Swiss army knife of OSGi, it is used for creating and working with OSGi bundles. Its primary goal is take the pain out of developing bundles. With OSGi you are forced to provide additional metadata in the JAR’s manifest to verify the consistency of your “class path”. This metadata must be closely aligned with the class files in the bundle and the policies that a company has about versioning. Maintaining this metdata is an error prone chore because many aspects are redundant.
+ +bnd’s raison d’etre is therefore to remove the chores and use the redundancy to create the manifest from the class files instead of maintaining it by hand. The core task is therefore to analyze the class files and find any dependencies. These dependencies are then merged with ‘‘instructions’’ supplied by the user. For example, adding a version to all imported packages from a specific library can be specified as:
+ +Import-Package: com.library.*; version = 1.21
+
The OSGi manifest must explicitly mention a package, bnd allows the use of wildcards. bnd contains many more such conveniences. bnd roots are about 10 years old and bnd has therefore a large number of functions that remove such chores. These range from simplifying the use of OSGi Declarative Services, working with Spring and Blueprint, WAR and WAB files, version analysis, project dependencies, and much more.
+ +Over time bnd started to appear in many different incarnations. It is an an ant task, a command line utility, and a bundle for Eclipse. Other projects have used bndlib to create a maven plugin, bndtools and Sigil both Eclipse IDEs, and others. By keeping the core library small and uncoupled (bnd has no external connections except Java 5), it is easy to embed the functionality in other projects.
+ +Traditionally, JAR files were made with the JDK jar tool, the jar ant task, or the Maven packager. All these tools share the same concept. The developer creates a directory image of the jar by copying files to a directory; this directory is then jarred. This model can be called the ‘‘push’’ model. Obviously this method works well.
+ +bnd works differently, it uses the ‘‘pull’’ model. Instructions in the bnd file describe the contents of the desired JAR file without writing this structure to disk. The contents from the output can come from the class path or from anywhere in the file system. For example, the following instruction includes the designated packages in the JAR:
+ +Private-Package: com.example.*
+ +bnd can create a JAR from packages the sources, directories or other JAR files. You never have to copy files around, the instructions that Bnd receives are sufficient to retrieve the files from their original location, preprocessing or filtering when required.
+ +The Jar is constructed from 3 different arguments:
+ +Export-Package
+Private-Package
+Include-Resource
+
Private-Package and Export-Package contain ‘‘instructions’’. Instructions are patterns + attributes and directives, looking like normal OSGi attributes and directives. For example:
+ +Export-Package: com.acme.*;version=1.2
+
Each instruction is applied to each package on the classpath in the definition order. That is, if an earlier instruction matches, the later instruction never gets a chance to do its work. If an instruction matches its attributes and properties are applied to the packages. The difference between the Private-Package argument and the Export-Package arguments is that the export version selects the packages for export. If the packages overlap between the two, the export wins.
+ +An instruction can also be negative when it starts with a ‘!’. In that case the package is excluded from the selection. For example:
+ +Export-Package: !com.acme.impl, com.acme.*;version=1.2
+
Note that the instructions are applied in order. If the ! instruction was at the end in the previous example, it would not have done its work because the com.acme.* would already have matched.
+ +The Include-Resource argument can be used to copy resources from the file system in the JAR. This is useful for licenses, images, etc. The instructions in the argument can be a directory, a file, or an inline JAR. The default JAR path is the the root for a directory or the filename for a file. The path can be overridden. Instructions that are enclosed in curly braces, like {license.txt}, are pre-processed, expanding any macros in the file.
+ +Once the JAR is created, the bnd program analyzes the classes and creates an import list with all the packages that are not contained in the jar but which are referred to. This import list is matched against the Import-Package instructions. Normally, the Import-Package argument is *; all referred packages will be imported. However, sometimes it is necessary to ignore an import or provide attributes on the import statement. For example, make the import optional or discard the import:
+ +Import-Package: !com.acme.*, *;resolution:=optional
+
The arguments to bnd are normal given as a set of properties. Properties that begin with an upper case are copied to the manifest (possibly after processing). Lower case properties are used for macro variables but are not set as headers in the manifest.
+ +After the JAR is created, the bnd program will verify the result. This will check the resulting manifest in painstaking detail.
+ +The bnd program works on a higher level than the traditional jarring; this might take some getting used to. However, it is much more elegant to think in packages than that it is to think in files. The fact that the bnd understands the semantics of a bundle, allows it to detect many errors and also allows bundles to be created with almost no special information.
+ +bnd will not create an output file if none of the resources is newer than an existing output file.
+ +The program is available in several forms: command line, ant task, maven plugin, and an Eclipse plugin.
+ +There are some common pitfalls that can be prevented by following the tips:
+ +One of the more surprising things we’ve learned about modularity in the last decade is how much of a fractal pattern it actually is. All the way from CPU machine instructions all the way up to a large distributed system you can see the pattern of encapsulation to keep as much details as possible confined to the internals of the module. Strangely enough, as an industry we seem to have a hard time learning the lessons from the lower layers where we over time learned the rules to the layers above.
+ +Java was developed the early 90’s as a state of the art language based on Object Oriented concepts. It did apply the rules of modularity on the method and class level and to pioneered with providing a higher level module with their concept of packages. However, our software industry did their utmost to ignore this concept, helped by the fact that it did have some real shortcomings.
+ +The key shortcoming was that it was hard to keep classes private since they required public visibility when they crossed the package boundary. This collided with the granularity of the package, in general you needed to break an application in many different packages to stay sane as a developer; with the consequence that a lot of private code was forced to become public.
+ +Java did provide a higher level module: the JAR. However, this was a concept outside the language, it was only visible in runtime to be used by the class loaders. Interestingly, it was then used in runtime security but it was kept outside the language that still desperately tried to front a single name space. Problems showed up as class cast exceptions, package private access violations in the same package, and getting the wrong class. It is amazing how brittle the class path model of Java really is.
+ +In search for a model that would allow the management of hundreds of thousands of devices running Java the OSGi decided to leverage the JAR but turn it in to a proper module by adding encapsulation. Since in modularity a module tends to export/import entities that have a lower granularity (classes export/import methods, packages import/export classes), the OSGi solution was to export/import packages.
+ +Package imports/export was a much maligned choice because the maligners did not understand that some of the lessons learned from object oriented programming, where the class is the module, also applied to the other kinds of modules. The greatest problem with Object Oriented was that direct object references created highly coupled systems that were very brittle. Direct references aggregate many constraints that are only important for a specific implementation. This problem was not fully solved until we got Java interfaces. Java interfaces allow a user of that interface and an implementer of that interface to only couple on the relevant details necessary for the collaboration; any implementation details on either side of the interface are not constraining the other side in any way.
+ +What the maligners do not see is that we face the exact same problem when we turn JAR into modules. The maven model only supports direct references between modules and they aggregate implementation constraints in the same way as object references did. The solution the OSGi came up with is to treat a package as a specification, just like a Java interface specifies the behavior of an implementation class.
+ +In this way a bundle (the OSGi JAR module) could provide an implementation for a (specification) package and other bundles could consume this implementation. Again, this is similar to the Java interface for classes.
+ +In the Java world we learned rather slowly that interfaces are great but that you have this pesky problem of how to get an instance for that interface if you should not be coupled to a specific implementation? Java created a a number of quite horrendous factory models for almost each subsystem, a factory model that too often bled into the actual collaborative API. For example, the javax.persistence
package does not only contain the classes to work with JPA, it also contains a surprisingly large API to manage the life cycle of the provider. Factories also heavily abused class loaders for a purpose they were clearly not designed for. In Java 6 Sun tried to stop the hemorrhaging with the Service loader but forgot that class loaders are not designed for this purpose and baked them right into the API.
Spring brought dependency injection to the masses that allowed the consumers and providers to stay away from each other but it then introduced a massive coupling in the XML that contained too many details about the provider implementations since it specified the provider class names.
+ +The solution OSGi came up with in 1998 was to use a service broker. A broker makes sure that consumers and providers were bound together once they are available. This is the only true modular model that allows all implementation details to remain private inside the module.
+ + + +A the consequence of this model is that services, an implementation object implementing a Java interface from a specification package, cannot be assumed to be always there. Where Spring calculates the dependencies of its beans to initialize a system in the right order, a broker negotiates when a service is available since it does know any of its implementation details. One of those implementation details is when that service will be available. The broker is therefore dynamic, it can react to changing circumstances, another even more both maligned and misunderstood OSGi feature.
+ +The hardest part of OSGi is to look at it with a clean slate. The core model is surprisingly simple, elegant, and extremely powerful. The hard part is that it tends to conflict (as it should!) with all the bad habits in existing code bases that grew up thinking class loaders were simple extension mechanisms.
+ +So to summarize, a bundle is a JAR that imports and exports a set of packages. These imports and exports are bound to other bundles when they are resolved, allowing multiple versions of the same package. Once a bundle is started, it can then communicate with the external world at its own discretion; it can also get and register services to collaborate with other bundles. The following picture depicts the model of an OSGi application:
+ + + +The symbols used are defined in the OSGi specifications. The rectangle with rounded corners is a bundle, the triangle is a service (it always points in the dependency direction, i.e. the Reporter bundle depends on the sensor service to be there. Input and output is depicted with the corresponding flow chart symbol.
+ +In a perfect OSGi world, packages are just a minor detail. Until then, we also need some way to show package imports and package exports. In the OSGi specification, we use an open rectangle for imported packages and and black rectangle for exported packages. Private packages are depicted with a grey rectangle, see:
+ + + +There is often a confusion of terminology, like, is a bundle a component? We are guilty of not always using the terminology consistent. However, in the past few years it has become very clear that the OSGi declarative services provides a programming model that should have been incorporated in the OSGi framework from the beginning. DS allows you to make any object active with a simple annotation. This object can automatically be registered as a service. If there are dependencies on other services then you can easily specify those dependencies with an annotation on a set method.
+ +In DS, the implementation class is called the component and in this document we follow this lead. Therefore, in general a bundle consists of a number of components.
+ +The bndlib workspace is an encapsulation of a set of cohesive projects, where a project exports zero or more bundles via repositories. A repository provides access to set of bundles exported by some means, likely from other workspaces. A repository can be on the local file system or a remote system like Maven central.
+ +Projects can depend on other projects in the workspace or import bundles from the repositories. This is depicted in the following figure.
+ + + +A workspace is a single directory, just like a git workspace it encompasses all its sub directories. Though the name of the workspace directory is free to choose, it is highly recommended to use a naming strategy. In practice you will create many different workspaces and having a naming strategy will significantly simplify the handling of these workspaces.
+ +On the same root level in the workspace as the cnf
directory, bndlib expects the projects. Yes, projects must reside in the root level. The reason is again, simplicity. We will later discuss how the bundle’s symbolic name is derived from the project’s directory name. Since projects can depend on each other, bndlib maintains a workspace repository of projects that it derives from the top level directories. Some people desperately want to use hierarchies of projects (often because that is how they used to work before). However, even people patching bndlib to make it hierarchical admit that the simple linear model is actually working quite well. The reason it works so well is that a workspace is not supposed to hold every single bundle that your organization produces. It is intended to be a cohesive set of between 10-20 up to a couple of hundred projects.
The idea of a workspace is that it is cohesive. It contains a set of shared bundles and some of those bundles are exported to a repository. Other bundles are imported from repositories. This is the basic model of modularity.
+ +This strongly implies that you should NOT share cnf between workspace, just like you should not share private fields from classes nor private classes from packages. So the idea is that your organizations has a number of workspaces that contain the code that has a strong relation. For example a product, your company service APIs, or shared base libraries. Since all these projects are in one workspace you get a lot of ease of use like refactoring and immediate feedback.
+ +That said, there is often a desire to have some of the information, like the repositories in a shared place because you do not want to maintain it multiple times. Since bnd is completely based on inherited properties this is not that hard. The top of the properties are coming from the cnf/build.bnd file. Since this is a bnd file, you can actually include another bnd file there. Since this can be included via a URL, you can refer it to a file on your git repository. For convenience, you could make it refer to master but a better way is to make it refer to an actual commit. The reason is that if you checkout your project 5 years from now, it is unlikely that your build will be ok with the latest version of the included bnd file.
+ +build.bnd:
+
+-include: https://examplegit.com/foo/bar/master/shared/shared.bnd
+
A more defined way to handle this long term versioning problem is to use git modules. They have a bad name but as far as I understand their bad name is because people don’t like that they are not automatically updated, which is exactly what you want when you want to build your project 5 years from now. Git modules require an explicit command to upgrade it to the latest or another version. The parent git repository stores the commit at which it is linked. With git modules you could make a subdirectory in cnf and then include a shared file from there:
+ +cnf
+ shared/
+ .git/
+ shared.bnd
+ build.bnd
+
+build.bnd:
+-include shared/shared.bnd
+
So to summarize, share workspaces, not projects. Use continuous integration that publishes your bundles to a repository that is shared with all. Share one bnd file with a git module.
+ + +This chapter lays out the rules of the file system for bnd projects. It discusses the workspace layout and the projects layout as well as the properties.
+ +A Workspace is a single directory with all its sub-directories and their files, similar to a git workspace. The core idea of the workspace is that it is easy to move around, that is, it allows the use of relative file names. It also prevents a lot of potential problems that occur when you allow projects to be anywhere on the file system. KISS!
+ +Workspaces should be named according to the bundle symbolic names of its projects. Using such a naming strategy will simplify finding the correct namespace given a bundle symbolic name.
+ +A bndlib workspace is a valid workspace when it contains a cnf
file. If this is a text file, its content is read and interpreted as a path to the cnf
directory (which can again be a path to a cnf directory, ad infinitum). The retrieved path is retrieved and trimmed after which it is resolved relative to its parent directory.
However, the advised model is to use a directory with a cnf/build.bnd
file. The purpose of the cnf
directory is to provide a place for shared information. Though this includes bndlib setup information, it also can be used to define for example shared licensing, copyright, and vendor headers for your organization.
The cnf
directory can have an ext
directory, this directory contains any extensions to bnd.
To cache some intermediate files, bndlib will create a cnf/cache/
directory, this file should not be under source control. E.g. in Git it should be defined in the .gitignore
file.
The root of the workspace is generally used to hold other files. For example the .git
directory for Git, or gradle and ant files for continuous integration. However, designers that add functionality to the workspace should strive to minimize the clutter in the root. For example, the bnd gradle support adds a few files to the root but these link to a cnf/gradle
directory for their actual content.
Other directories in the workspace represent projects. The name of the project is the bundle symbolic name of the bundle that it produces (or the prefix of the bundle symbolic name when it produces multiple bundles).
+ +Overall, this gives us the following layout for a workspace:
+ +com.acme.prime/ workspace
+ cnf/ configuration/setup
+ ext/ extensions
+ maven.bnd maven setup extension
+ build.bnd organization setup
+ plugins/ directory for plugins
+ cache/ bnd cache directory, should not be saved in an scm
+ com.acme.prime.speaker/ project directory
+
Properties are used for headers, macros, and instructions in bndlib, they are quite fundamental. To simplify maintenance, bndlib provides an elaborate mechanism to provide these properties from different places and inherit them. The workspace resides at the top of this inheritance chain (ok, after the built-in defaults).
+ +When a workspace is created, it will first read in the properties in the .bnd
files in the cnf/ext
directory. These are called the extension files since they in general setup plugins and other extensions. The order in which they are read is the lexical sorting order of their file names.
The target-dir
defines where the build process places the build output artifacts. By default this folder is named generated
.
In the default setup of the workspace, the gradle build tool & eclipse share the same output directories. In general, this means you always have to clean in each tool and in the case of Eclipse stop the incremental builder (otherwise eclipse will start rebuilding the whole workspace when you do a gradle clean build
).
All the output directories are defined in macros. In general, the ${target-dir}
is the main output directory and the ${bin}
and ${testbin}
are placed inside this directory. So by redefining ${target-dir}
in cnf/build.bnd
we can redirect all output.
bnd has a macro ${driver}
that indicates which build tool (a.k.a. the driver) is used. We can then use this as follows, to use a separate output directory for each build tool (e.g. eclipse, gradle, maven):
target-dir generated${if;${driver;eclipse};;/${driver}}
+bin ${target-dir}/classes
+testbin ${target-dir}/test-classes
+
This example configuration, placed in cnf/build.bnd
means:
generated
folder of each project.generated/gradle
.Extension files allow you to separate configuration concerns. Its primary purpose is to allow third party extensions. These extensions can then put their properties in one place. The contents of these files should therefore not be touched so that a new version can override them. Each extension file is read as a bnd file, this means that full power of bndlib is available. The bnd command line tool has facilities to add and remove files from this directory.
+ +For example, the Maven plugin that is built-in to bndlib has an extension file called maven.bnd which looks as follows:
+ +#
+# Plugin maven setup
+#
+-plugin.maven = aQute.bnd.plugin.maven.MavenPlugin
+
+
+#
+# Change disk layout to fit maven
+#
+
+-outputmask = ${@bsn}-${versionmask;===S;${@version}}.jar
+src=src/main/java
+bin=target/classes
+testsrc=src/test/java
+testbin=target/test-classes
+target-dir=target
+
We will not explain this plugin here (you can find it in the plugin sections), it only illustrates here how it is possible to setup the environment for a specific optional functionality like a plugin.
+ +If you create local extension files then you should use a prefix to identify this is your file, like:
+ +com.acme-local.bnd
+
It is possible to use file links to maintain these files in one place when you have many workspaces.
+ +Extension files will be loaded in alphabetical order. If a files defines a property that was already defined in another previously loaded file or the build.bnd
, the property will be assigned a namespace. -plugin
in /cnf/ext/test.bnd
will become -plugin.test
. Please note that this mechanism is just a best guess courtesy and has its limits. If -plugin.test
was already defined previously, the old value will be overwritten.
After reading the extension files, bndlib reads the cnf/build.bnd
file, this file is supposed to hold the organization specific properties. Out of the box, this file comes empty, ready to be filled by you.
A plugin is a piece of code that runs inside bnd. The workspace provides a number of standard built-in plugins like an Executor, a Randum number generator, itself, etc. Additional plugins can be added with the -plugin.*
instructions.
There are a number of built in properties that are set by bnd:
+ +Property name | +Description | +
---|---|
project |
+ Name of the project. This is the name of the bnd file without | +
+ | the .bnd extension. If this name is bnd.bnd, then the directory | +
+ | name is used. | +
project.file |
+ Absolute path of the main bnd file. | +
project.name |
+ Just the name part of the file path | +
project.dir |
+ The absolute path of the directory in which the bnd file resides. | +
Run instructions are used to start OSGi tests and OSGi runs.
+ +-runbundles |
+ LIST SPEC |
+ Additional bundles that will be installed and started when the framework is launched. This property is normally part of the project’s bnd.bnd file. |
+
-runvm |
+ PROPERTIES |
+ Properties given to the VM before launching. This property is normally set in the cnf/build.bnd file and only in rare cases overridden in the bnd.bnd file. |
+
-runproperties |
+ PROPERTIES |
+ Properties given to the framework when launching. Usually project specific. | +
-runsystempackages |
+ PACKAGES |
+ A declaration like Import-Package that specifies additional system packages to import from the class path. Usually given in the cnf/build.bnd file. |
+
-runpath |
+ LIST SPEC |
+ A path description of artifacts that must be on the classpath of the to be launched framework. Usually given in the cnf/build.bnd file. This path should contain the framework. Any packages that a bundle on the -runpath should specify should be listed in the export attribute. |
+
-runtrace |
+ true|false |
+ Trace the startup of the framework to the console. Usually used during testing and development so project specific. | +
-runframework |
+ none|services |
+ NONE indicates that a mini built in framework should be used. SERVICES indicates that the META-INF/services model must be followed for the org.osgi.framework.launch.FrameworkFactory class. Project specific. |
+
-testpath |
+ LIST SPEC |
+ A path used to specify the test plugin. | +
Launching is needed when the project’s run
action or test
action is executed. The project creates a Project Launcher. A Project Launcher must launch a new VM and set up this VM correctly. The VM is launched with the following information:
java
- The command to launch a new VM is by default java
. However, this can be overridden by setting a property called java
.classpath
- The classpath set for the VM is derived from the -runpath
property. Notice that this is supposed to contain the JAR with the framework. The -runpath
requires bundle symbolic names for the JAR and an optional version range. bnd will use the latest version found in the repository. Any packages that should be exported by the system bundle should have an export
attribute containing the exported packages, like junit.osgi;version=3.8;export="junit.framework;version=3.8,junit.misc;version=3.8"
.-runvm
property. They are usually in the form of -Dxya=15
or -X:agent=bla
. Options should be separated by commas.main
- The class implementing the main type is defined by the launcher plugin.An example of a launcher set is:
+ +-runvm: -Xmn100M, -Xms500M, -Xmx500M
+-runpath: \
+ org.apache.felix.framework; version=3.0, \
+ junit.osgi;export="junit.framework;version=3.8"
+-runtrace: true
+-runproperties: launch=42, trace.xyz=true
+-runbundles: org.apache.felix.configadmin,\
+ org.apache.felix.log,\
+ org.apache.felix.scr,\
+ org.apache.felix.http.jetty,\
+ org.apache.felix.metatype,\
+ org.apache.felix.webconsole
+
Debugging the launcher is greatly simplified with the -runtrace
property set to true
. This provides a lot of feedback what the launcher is doing.
When the Framework is started the Launcher will register itself as a service of type java.lang.Object
with the property launcher.arguments
to provide access the arguments handed to the launcher. After all Bundles have been installed and started the ServiceRegistration will be updated with the property launcher.ready=true
.
In certain cases it is necessary to grab the main thread after launching. The default launcher will launch all the bundles and then wait for any of those bundles to register a Runnable
service with a service property main.thread=true
. If such service is registered, the launcher will call the run method and exit when this method returns.
The launcher will timeout after an hour. There is currently no way to override this timeout.
+ +The bnd launcher contains a mini framework that implements the bare bones of an OSGi framework. The purpose of this mini framework is to allow tests and runs that want to launch their own framework. A launch that wants to take advantage of this can launch with the following property:
+ +-runframework: none
+
In Ant, the following task provides the run facility.
+ +<target name="run" depends="compile">
+ <bnd command="run" exceptions="true" basedir="${project}" />
+</target>
+
These targets provide commands for ant run
.
Testing is in principle the same as launching, it actually uses the launcher. Testing commences with the test
action in the project. This creates a Project Tester. bnd carries a default Project Tester but this can be overridden.
The basic model of the default Project Tester plugin is to look for bundles that have a Test-Cases
manifest header after launching. The value of this header is a comma separated list of JUnit test case class names. For example:
Test-Cases: test.LaunchTest, test.OtherTest
+
Maintaining this list can be cumbersome and for that reason the ${classes}
macro can be used to calculate its contents:
Test-Cases: ${classes;extending;junit.framework.TestCase;concrete}
+
See classes macro for more information.
+ +<target name="test" depends="compile">
+ <bnd command="test" exceptions="true" basedir="${project}" />
+</target>
+
Both the Project Launcher and Project Tester are plugins. Defaults are provided by bnd itself (bnd carries a mini cache repo that is expanded in the cnf
directory), it is possible to add new launchers and testers as needed.
Launcher or tester plugins are found on the -runpath
or the -testpath
properties, respectively. To detect a plugin, bnd will look for an appropriate manifest header. The header value is a class name. Bnd will then instantiate the class and use it as a launcher/tester. The classes must extend an abstract base class. Each plugin has access to the Project
object, containing all the details of the project.
Plugin | +Manifest header | +Base Class | +Where searched | +
---|---|---|---|
Project Launcher | +Launcher-Plugin | +ProjectLauncher | +-runpath | +
Project Tester | +Tester-Plugin | +ProjectLauncher | +-testpath | +
The plugin gets complete control and can implement many different strategies.
+ +This is about generating JARs
+ +This is a simple example of wrapping a jar with bnd. The basic idea is to create a recipe (a .bnd file) that collects the different resources in the right way.
+ +For example, you want to wrap the WebSocket server from https://github.com/TooTallNate/Java-WebSocket. Download the binary and the sources in Websocket.jar, Websocket-src.zip. Once you have these files, the following code will create the org.websocket bundle
+ +# Wrapped version of Github project TooTallNate/Java-WebSocket
+Bundle-SymbolicName: org.websocket
+Bundle-DocURL: https://github.com/TooTallNate/Java-WebSocket
+Bundle-License: https://github.com/TooTallNate/Java-WebSocket/blob/8ef67b46ecc927d5521849dcc2d85d10f9789c20/LICENSE
+Bundle-Description: This repository contains a barebones \
+ WebSocket server and client implementation written \
+ in 100% Java. The underlying classes are implemented \
+ using the Java ServerSocketChannel and SocketChannel \
+ classes, which allows for a non-blocking event-driven model \
+ (similar to the WebSocket API for web browsers). \
+ Implemented WebSocket protocol versions are: Hixie 75, \
+ Hixie 76, Hybi 10, and Hybi 17
+
+# websocket does not define a version yet :-(
+Bundle-Version: 1.0.2
+
+-includeresource: @jar/WebSocket.jar, OSGI-OPT/src=@jar/WebSocket-src.zip
+-exportcontents: org.java_websocket
+
The documentation headers are optional but very important, just spent the minute to document them since you’ll be grateful later.
+ +If the target project does not have a version, makeup a version and maintain it. Notice that in general the recipe will only be used once for each version, it is normally not used in continuous integration builds. However, you normally use it to convert the next version of the project. Crisp versioning is important.
+ +The Include-Resource
statement unrolls the jars we downloaded in the root of the JAR and in OSGI-OPT
. Since the source code is in the src
directory in the WebSocket-src.zip
file, we put it in the new JAR under OSGI-OPT/src
. This convention is supported by all IDEs to give you direct access to the bundle’s source code. Since the binary and the source are kept together, you always have the correct source code available, and usually automatically. It is so convenient that once you’re used to this it is hard to imagine a life without source code.
The binaries and sources are not in the final jar but bnd does not yet know what needs to be exported. This can be indicated with the -exportcontents
instruction. It has the same syntax as Export-Package
but does not copy from the classpath, it only applies the instruction to the content of the final JAR.
The easiest way to build these wrappers is to create a project in bndtools called wrappers and create a bnd descriptor for each one. They are then automatically build (look in generated) and you get a lot of help editing the bnd files.
+ +You can also make an ant file but this is not described here (volunteer?). The other possibility is to use bnd from the [[CommandLine]]. In this case the command is:
+ +bnd websocket.bnd
+
Applying this recipe gives the following manifest in a JAR named org.websocket.jar
:
Manifest-Version: 1.0
+Bnd-LastModified: 1338190175969
+Bundle-Description: This repository contains a barebones WebSocket serve
+ r and client implementation written in 100% Java. The underlying classe
+ s are implemented using the Java ServerSocketChannel and SocketChannel
+ classes, which allows for a non-blocking event-driven model (similar to
+ the WebSocket API for web browsers). Implemented WebSocket protocol ve
+ rsions are: Hixie 75, Hixie 76, Hybi 10, and Hybi 17
+Bundle-DocURL: https://github.com/TooTallNate/Java-WebSocket
+Bundle-License: https://github.com/TooTallNate/Java-WebSocket/blob/8ef67
+ b46ecc927d5521849dcc2d85d10f9789c20/LICENSE
+Bundle-ManifestVersion: 2
+Bundle-Name: org.websocket
+Bundle-SymbolicName: org.websocket
+Bundle-Version: 1.0.2
+Created-By: 1.6.0_27 (Apple Inc.)
+Export-Package: org.java_websocket;version="1.0.2"
+Include-Resource: @jar/WebSocket.jar, OSGI-OPT/src=@jar/WebSocket-src.zip
+Private-Package: org.java_websocket.handshake,org.java_websocket.drafts,
+ org.java_websocket.exceptions,org.java_websocket.util,org.java_websocke
+ t.framing
+Tool: Bnd-1.51.0
+
One of the great features of bnd is to use export version from other versions to generate the import ranges. This feature requires that the other JARs are on the classpath. In bndtools you can use the -buildpath. However, you always add entries on the class path per bnd descriptor with the -classpath instruction:
+ +-classpath: dependency.jar, other.jar
+
Versioning is probably the most painful part of developing real software. Where toys and prototypes can be developed ignoring evolution, real software requires a migration path to an unknown future.
+ +The OSGi has defined a versioning policy that is described in the Semantic Versioning whitepaper. bnd fully supports this model and provides many shortcuts. The goal of bnd is remove any manual work from versioning bundles as well as packages.
+ +The key concept to version in OSGi is the ‘‘package’’. Bundles are an ‘‘aggregate’’ and therefore must move as fast as the fastest moving exported packages they contain. For example, if a bundle contains two exported packages foo
and bar
and foo
is not changed but bar
has a major change, then the bundle version needs to also have a major change. This requires an unnecessary update for a bundle that only depended on foo
. Aggregating dependencies increases the fan out of the transitive dependencies. The result is that systems can only evolve when everything is updated simultaneously. The result is that the system as a whole becomes brittle.
In contrast, versioning the packages and using Import-Package, bundles can be refactored and versioned independently.
+ +provide:=true
directive on the package export, e.g. Export-Package: org.osgi.service.event; provide:=true
.A version in OSGi has 4 parts:
+ +major 1 + minor 1.1 + micro 1.1.1 + qualifier 1.1.1.qualifier
+ +To survive versioning, one must have a ‘‘version policy’’. A version policy puts semantics on the version numbers. The ‘‘recommended’’ policy in OSGi is changing the part when there is:
+ +major a breaking change + minor a backward compatible changes + micro a bug fix (no API change) + qualifier a new build
+ +In OSGi, the decision was taken to have a single export version. The import statement allows a version range to be set. For example:
+ +Export-Package: com.acme.foo; version=1.0.2
+Import-Package: com.acme.bar; version="[1,2)"
+
The semantic versioning white paper introduces two terms that are orthogonal to the imports and exports as well as implementing or delegating:
+ +Provide and consume is orthogonal to implementing an interface and delegating. For example, the Configuration Admin service has the ConfigurationAdmin
interface that is implemented by the Provider of an API but the ConfigurationListener
interface is implemented by the Consumer of the API.
The reason for the providers and consumer terms is that version policies are different. A change in an API almost always affects the provider but with careful API design it is often possible to make a change backward compatible for consumers.
+ +In bnd, whenever you have to provide an import range, you can use modifiers to create a range out of a single version:
+ +@1.2.3
– Creates an import up to the next major version: [1.2.3,2.0.0)
.1.2.3@
– Creates an import up to the next minor version: [1.2.3,1.3.0)
.=1.2.3
– Creates a range that only accepts that version: [1.2.3,1.2.3]
.If you have a package that is containing implementation code that is supposed to be directly used by the consumers then this is a ‘‘library’’.
+A library package is not an API that can be implemented by other bundles, it is the implementation. Then the versioning of library packages is relatively straightforward: Any change that breaks a consumer of that package must increment the major version number. For example, if the popular ASM library would add a method to the MethodVisitor
class then it must increment the major version number of the org.objectweb.asm
package because all existing consumers of this library would then be broken.
If however a package contains an API that is provided and consumed by others the situation is more complex. In such a case, the provider should export the API package and the consumers should import it.
+ +bnd explicitly allows the inclusion of packages that come from other projects. It is just good practice to include an API package in your bundle if you are the provider of that API. However, this means that maintaining the version of the package in the manifest is ‘‘wrong’’, it would have to be maintained in several places, which is very error prone.
+ +For this reason, bnd provides a way to store the version of the package together with the package itself. One with annotations and one without when annotations are not possible.
+ +The @Version annotation is placed on the package. Since Java 5 it is possible to create a package-info.java file that can be used to annotate packages:
+ +package-info.java:
+ @org.osgi.annotation.versioning.Version.Version("1.2.0")
+ package com.example;
+
A non-annotation based alternative is the packageinfo
file. When bnd scans the Java archives it will look in each package for this packageinfo file. The format of this file is very simple:
packageinfo:
+ version 1.2.0
+
If you now export the package (from any bundle that has the package on its class path), it will be properly versioned.
+ +bnd.bnd:
+ Export-Package: com.example.*
+
The resulting manifest will look like:
+ +Manifest:
+ Export-Package: com.example; version=1.2.0
+
If you export a package from another bundle, bnd will also look in the manifest of that other bundle for a version.
+ +Using packageinfo (or the @Version annotation) is highly recommended.
+ +The package version for package p can come from the following places (in order of increasing priority):
+ +p/packageinfo
p/package-info.java
with an annotationThe bnd warning means that bnd finds multiple definitions of the version for p and they are not the same. So I assume you set the version in the manifest of the bundle under construction and in one of those other places. The best is to remove the version from your bnd.bnd file. The by far absolute best way is to only set the version of the package in the package directory (either packageinfo
or package-info.java
with an annotation).
If you import a package bnd will look at the exported version of that package. This version is not directly suitable for the import because it is usually too specific, it needs a policy to convert this export version to an import version.
+ +An importer that provides the functionality of an API package is much closer tied to that package than a client. The whitepaper recommends binding to the major.minor part of the version for a provider. That is, any change in the minor part of the version breaks the compatibility. This makes sense, the provider of an API must implement the contract and is therefore not backward compatible for any change in the API. A consumer of the API only has to be bound to the major part because it is much more relaxed for the backward compatibility.
+ +For example, a new method is added to an interface that is implemented by the provider of the API. Old clients have no visibility of this method because when they compiled it did not exist. However, the provider of the API must be modified to implement this method otherwise more modern clients would break.
+ +This asymmetry creates the need for two version policies:
+ +-provider-policy : ${range;[==,=+)}
+-consumer-policy : ${range;[==,+)}
+
The given values are the defaults. The value of the version policy will be used calculate the import based on the exported package. The ${range}
macro provides a convenient shortcut to do this using a version mask.
For example, a bundle that implements the OSGi Event Admin service can use the following bnd file:
+ +bnd.bnd:
+ Private-Package: com.example.impl.event
+
The resulting manifest would look like:
+ +Manifest:
+ Import-Package: org.osgi.service.event; version="[1.1,2)", ...
+ ...
+
How does bnd know if a bundle is a provider or a consumer of a specific package? Well, the default is the consumer policy but this can be overridden with the provide:=true
directive that works on the Import-Package
clauses as well as on the Export-Package
clauses.
The provide:
directive indicates to bnd that the given package contains API that is provided by this bundle. The (strongly) recommended way is to put the provide:=true
directive on the Export-Package
header, even if the package comes from another bundle. This way the bundle contains a copy of the package that is by default imported with the proper provider policy range.
For example, an implementation of the OSGi Event Admin specification could use the following bnd file:
+ +bnd.bnd:
+ Export-Package: org.osgi.service.event; provide:=true
+ Private-Package: com.example.impl.event
+
The resulting manifest would look like:
+ +Manifest:
+ Export-Package: org.osgi.service.event; version=1.1
+ Import-Package: org.osgi.service.event; version="[1.1,1.2)", ...
+ ...
+
If for some reason it is not desirable to export the API package in the implementation bundle, then the provide:
directive can also be applied on the Import-Package
header:
bnd.bnd
+ Import-Package: org.osgi.service.event; provide:=true, *
+ Private-Package: com.example.impl.event
+
The resulting manifest would look like:
+ +Manifest:
+ Import-Package: org.osgi.service.event; version="[1.1,1.2)", ...
+ ...
+
A key aspect of OSGi is that a package can be both imported and exported. The reason is that this feature allows a framework more leeway during resolving without creating multiple unconnected class spaces.
+ +After the bundle has been created and analyzed bnd will see if an exported package is eligible for import. An export is automatically imported when the following are true:
+ +-noimport:
directive.If a package is imported it will use the version as defined by the version policy.
+ +Versioning bundles usually requires bumping the version every time it is placed in a repository. When package versioning is used, the bundle version is only important for tracking an artifact.
+ +The micro is left out because it generates a lot of unnecessary releases, this is similar to the maven release process. If you connect everything 100%, you cannot move anything unless all its dependencies are moved at the same time. We actually tried in the OSGi build to use micro version changes for default methods in Java 8 but found that it just creates an enormous ripple through effect in the build. Not depending on the micro version is a lubricant that does not kill any bundle out there that depends on you.
+ +This should not be a problem because a micro version is a deployment issue since the semantic versioning should be used for APIs and a micro change is not visible in the API.
+ +That said, this is bnd so obviously you can override it. You can override the default version policy is:
+ +-provider-policy = ${range;[==,=+)}
+-consumer-policy = ${range;[==,+)}
+
Just set ‘===’ instead of ‘==’ for the floor version in your pom.xml in the
<configuration>
+ <_provider-policy>${range;[===,=+)}</_provider-policy>
+ <_consumer-policy>${range;[===,+)}</_consumer-policy>
+</configuration>
+
Baselining compares a bundle with another bundle, the baseline, to find mistakes in the semantic versioning. For example, there is a binary incompatible change in the new bundle but the version has not been bumped. Baselining can be run from the command line (see bnd help baseline
) or it can be run as part of a project build.
APIs are compared for backward compatibility using the semantic versioning rules defined in this chapter. Baselining is aware of the @ConsumerType
and @ProviderType
rules. Proper versions are calculated and suggested.
During the build, bnd will check the -baseline
instruction at the end of the build when the JAR is ready. This instruction is a selector on the symbolic name of the building bundle. If it matches, the baselining is started with one exception: the bundle version must be more than 1.0.0. If the version is less no baselining is possible, the purpose is to allow the primordial baseline to be established without errors.
By default the baseline is a bundle from one of the repositories with the same symbolic name as the building bundle and the highest possible version. However, it is possible to specify the version or to baseline against a file, see -baseline.
+ +It is recommended to enable baselining per project since not all project requires baselining. For example, baselining is overkill for a project that is always compiled with all its dependencies and thus has no external dependencies. The recommended practice is therefore to add the following instruction to a project that requires baselining (usually API bundles):
+ +-baseline: *
+
The default baseline is the bundle with the highest version in the baseline repository. The selector can specify a version
or a file
attribute when the default baseline is not applicable.
version
– Baseline against the first bundle in the baseline repository that has the given version or higher.file
– The file is the baseline.The baseline bundle is looked up in the baseline repository. The baseline repository is by default the release repository unless overridden by the -baselinerepo instruction. The release repository is set with the -releaserepo instruction.
+ +Only bundles that are not staging are considered for the baseline, this means that it is possible to release the current bundle and compare against the previous version until the bundle is released and becomes master.
+ +By default the bundle and baseline are compared (diffed) and then analyzed for semantic version violations. Certain headers always change because they contain time or digest information. Most of these headers are already automatically ignored but the -diffignore instruction can add more ignorance. You can use the +-diffpackages instruction to specify the names of exported packages to be baseline compared. The default is to baseline compare all exported packages.
+ + Bundle-Version: 1.0.2
+ -baseline: *
+ -baselinerepo: Released
+
In this example, the last version in the Released repository for the project’s bsn is supposed to be the previous version. Make sure you do not always release staging versions to this repository since this will create false changes. During a development cycle, the baseline version must remain constant until the current development bundle is released, at which points it becomes the baseline of the next cycle.
+ +Since an error is raised when the baselining detects a semantic version violation it is possible to release a snapshot in a build only when there is a correctly baselined bundle built.
+ +@BaselineIgnore
- Fine grained controlOccasionally, scenarios arise where a language construct or change is not accounted for in bnd (bnd developers are humans too), or where a developer wants to overrule the strict opinion of the baseline logic for whatever reason (not recommended but it does happen) for instance in the grey areas of binary compatibility.
+ +Instead of forcing developers to make a choice between disabling baseline (in order to avoid build warnings or failures) and keeping it enabled it’s important to know that there is fine grained control available using bnd’s @aQute.bnd.annotation.baseline.BaselineIgnore
annotation.
The value of the @BaselineIgnore
annotation is a valid OSGi version string.
e.g.
+ +@BaselineIgnore("2.4.12")
+public Foo getFoo();
+
When the @BaselineIgnore
annotation is applied to a baselined element, the baseliner will ignore the annotated element when baselining against a baseline package whose version is less than the specified version. This means the annotated element will not produce a baselining mismatch. The correct baseline information about the element will be in the baseline report, but the element will not cause baselining to fail. When baselining against a baseline package whose version is greater than or equal to the specified version, this annotation is ignored and the annotated element will be included in the baselining.
The annotation should be used in a scope that is as narrow as possible by applying it to the most specific member causing the baseline issue.
+ +The Service-Component header is compatible with the standard OSGi header syntax. Any element in the list that does not have attributes must have a resource in the JAR and is copied as is to the manifest. However, simple components can also be defined inline, and it is even possible to pickup annotations.
+ +The syntax for these component definitions is:
+ +component ::= <name> ( ';' parameter ) *
+parameter ::= provide | reference | multiple | optional
+ | reference | properties | factory | servicefactory
+ | immediate | enabled | implementation
+ | activate | deactivate | modified | configuration-policy
+ | version | designate
+
+reference ::= <name> '=' <interface-class>
+ ( '(' <target-filter> ')')? cardinality?
+cardinality ::= '?' | '*' | '+' | '~'
+provide ::= 'provide:=' LIST
+multiple ::= 'multiple:=' LIST
+optional ::= 'optional:=' LIST
+dynamic ::= 'dynamic:=' LIST
+designate ::= ( 'designate' | 'designateFactory' ) CLASS
+factory ::= 'factory:=' true | false
+servicefactory := 'servicefactory:=' true | false
+immediate ::= 'immediate:=' true | false
+enabled ::= 'enabled:=' true | false
+configuration-policy ::= "configuration-policy:='
+ ( 'optional' | 'require' | 'ignore' )
+activate ::= 'activate:=' METHOD
+modified ::= 'modified:=' METHOD
+deactivate::= 'deactivate:=' METHOD
+implementation::= 'implementation:=' <implementation-class>
+properties::= 'properties:=' key '=' value
+ ( ',' key '=' value ) *
+key ::= NAME (( '@' | ':' ) type )?
+value ::= value ( '|' value )*
+
If the name of the component maps to a resource, or ends in XML, or there are attributes set, then that clause is copied to the output Service-Component header.
+ +If the name can be expanded to one or more classes that have component annotations (they must be inside the JAR), then each of those classes is analyzed for its component annotations. These annotations are then merged with the attributes from the header, where the header attributes override annotations. The expansion uses the normal wildcard rules. For example, biz.aQute.components.*
will search for component annotated classes in the biz.aQute.components
package or one of its descendants. The classes must be present in the JAR. If no classes with annotations can be found for the name
then it is assumed to be name or implementation class name without annotations.
The name of the component is also the implementation class (unless overridden by the implementation: directive). It is then followed with a number of references and directives. A reference defines a name that can be used with the locateService
method from the ComponentContext
class. If the name starts with a lower case character, it is assume to be a bean property. In that case the reference is augmented with a set<Name>
and unset<Name>
method according to the standard bean rules. Bnd will interpret the header, read the annotations if possible, and create the corresponding resources in the output jar under the name OSGI-INF/<id>.xml
.
Annotations are only recognized on the component class, super classes are not inspected for the components.
+ +The supported annotations in the aQute.bnd.annotations.component
package are:
||!Component|| +Annotated the class, indicates this class is required to be a component. It has the following properties:
+ ++ | provide | ++ | Class[] | ++ | Service interfaces, the default is all directly implemented interfaces | ++ | + |
+ | name | ++ | String | ++ | Name of the component | ++ | + |
+ | factory | ++ | Boolean | ++ | Factory component | ++ | + |
+ | servicefactory | ++ | Boolean | ++ | Service Factory | ++ | + |
+ | immediate | ++ | Boolean | ++ | Immediate activation | ++ | + |
+ | designate | ++ | CLASS | ++ | Designate a class as a [[MetaType]] interface used for configurations for unitary configurations, see [[#metatype]]. This changes the default of the configurationPolicy to require . |
+ + | + |
+ | designateFactory | ++ | CLASS | ++ | Designate a class as a [[MetaType]] interface used for configurations for factory configurations, see [[#metatype]]. This changes the default of the configurationPolicy to require . |
+ + | + |
+ | configurationPolicy | ++ | OPTIONAL, REQUIRE, IGNORE | ++ | Configuration Policy | ++ | + |
+ | enabled | ++ | Boolean | ++ | Enabled component | ++ | + |
+ | properties | ++ | String[] | ++ | Properties specified as an array of key=value . The property type can be specified in the key as name:Integer . The value can contain multiple values, the parts must then be separated by a vertical bar (‘ |
+ ’) or a line feed (\n), for example properties = {"primes:Integer==1|2|3|5|7|11" }. |
+ + |
||!Reference|| +On a method. Indicates this method is the activate method. It has the following attributes
+ ++ | name | ++ | String | ++ | Name of the reference. Default this is the name of the method without set on it. | ++ |
+ | service | ++ | Class | ++ | The service type, default is the argument type of the method. The unset method is derived from this name. I.e. setXX will have an unsetXX method to unset the reference. | ++ |
+ | type | ++ | Character | ++ | Standard cardinality type ‘?’, ‘*’, ‘+’,’~’ | ++ |
+ | target | ++ | String | ++ | A filter expression applied to the properties of the target service | ++ |
+ | unbind | ++ | String | ++ | Optional name of the unbind method. By default this is the same name as the bind method name but with set/add replaced with unset/remove. E.g. setFoo() bind method becomes unsetFoo() unbind method. | ++ |
@Reference
automatically sets the bind method. The unbind method is set by using a derived name from the bind method or providing it with the name of the unbind method. The following name patterns are supported:
||!bind||!unbind||
+||setX
||unsetX
||
+||addX
||removeX
||
+||xxxX
||unxxxX
||
+For example:
@Reference
+protected void setFoo(LogService l) { ... }
+protected void unsetFoo(LogService l) { ... }
+
If you want to override this, use
+ +@Reference(unbind="IRefuseToCallMyMethodUnFoo");
+protected void foo(LogService l) {}
+protected void IRefuseToCallMyMethodUnFoo(LogService l) {}
+
Unfortunately Java has no method references so it is not type safe.A non existent @UnReference
annotation is not very useful because that still requires linking it up symbolically to the associated @Reference
.
||!Activate, Modified, and Deactivate|| +The life cycle methods. These annotations have no properties.
+ +Assume the JAR contains the following class:
+ +package com.acme;
+import org.osgi.service.event.*;
+import org.osgi.service.log.*;
+import aQute.bnd.annotation.component.*;
+
+@Component
+public class AnnotatedComponent implements EventHandler {
+ LogService log;
+
+ @Reference
+ void setLog(LogService log) { this.log=log; }
+
+ public void handleEvent(Event event) {
+ log.log(LogService.LOG_INFO, event.getTopic());
+ }
+}
+
The only thing necessary to register the Declarative Service component is to add the following Service-Component header:
+ +Service-Component: com.acme.*
+
This header will look for annotations in all com.acme sub-packages for an annotated component. The resulting XML will look like:
+ +OSGI-INF/com.acme.AnnotatedComponent.xml:
+ +<?xml version='1.0' encoding='utf-8'?>
+<component name='com.acme.AnnotatedComponent'>
+ <implementation class='com.acme.AnnotatedComponent'/>
+ <service>
+ <provide interface='org.osgi.service.event.EventHandler'/>
+ </service>
+ <reference name='log'
+ interface='org.osgi.service.log.LogService'
+ bind='setLog'
+ unbind='unsetLog'/>
+</component>
+
The following example shows a component that is bound to the log service via the setLog method without annotations:
+ +Service-Component=aQute.tutorial.component.World;
+ log=org.osgi.service.log.LogService
+
The Service Component Runtime (SCR) offers a variety of options on the reference. Some options like the target can be used by adding the target filter after the interface name (this likely requires putting quotes around the interface name+filter).
+ +References can be suffixed with the following characters to indicate their cardinality:
+ +Char Cardinality Policy
+? 0..1 dynamic
+* 0..n dynamic
++ 1..n dynamic
+~ 0..1 static
+ 1 static
+
For a more complex example:
+ +Service-Component=aQute.tutorial.component.World;
+ log=org.osgi.service.log.LogService?;
+ http=org.osgi.service.http.HttpService;
+ PROCESSORS="xierpa.service.processor.Processor(priority>1)+";
+ properties:="wazaabi=true"
+
The previous example will result in the following service component in the resource OSGI-INF/aQute.tutorial.component.World.xml
:
<?xml version="1.0" encoding="utf-8" ?>
+<component name="aQute.tutorial.component.World">
+ <implementation class="aQute.tutorial.component.World" />
+ <reference name="log"
+ interface="org.osgi.service.log.LogService"
+ cardinality="0..1"
+ bind="setLog"
+ unbind="unsetLog"
+ policy="dynamic" />
+ <reference name="http"
+ interface="org.osgi.service.http.HttpService"
+ bind="setHttp"
+ unbind="unsetHttp" />
+ <reference name="PROCESSORS"
+ interface="xierpa.service.processor.Processor"
+ cardinality="1..n"
+ policy="dynamic"
+ target="(&(priority>=1)(link=false))" />
+</component>
+
The description also supports the immediate, enabled, factory, target, servicefactory, configuration-policy, activate, deactivate, and modified attributes. Refer to the Declarative Services definition for their semantics.
+ +If any feature of the V1.1 namespace is used, then bnd will declare the namespace in the component
element. A specific namespace version can be set with the version
directive. This detection only works when components are used.
Bnd also supports setting the policy and cardinality through the following directives:
+ +multiple:= LIST names of references that have x..n
+optional:= LIST names of references that have 0..x
+dynamic:= LIST names of references that are dynamic
+
The Service Component Runtime works closely together with the Configuration Admin to allow the components to be controlled through configuration. Configuration Admin knows two types of configuration:
+ +A unitary configuration can be set and changed but there is at most one of them. A Factory configuration can be used to create multiple instances of the same component. A component has a configuration policy that defines when no configuration is set.
+ +A related standard is the Metatype standard. The Metatype Service provides a repository of property descriptors. Bundles can provide these descriptors in their bundles in the OSGI-INF/metatype directory. There are tools, like the Felix Webconsole, that can provide an editing window for a configuration that is typed with a metatype description.
+ +In practice, this is a powerful model that provides a lot of configurability for your components with easy editing but getting it all right is not trivial. To make this easier, bnd has made it ease to use configurations.
+ +In this model, configurations are declared in an interface. For example, the following interface defines a simple message:
+ +interface Config {
+ String message(); // message to give
+}
+
To create a component that can work with this config, we need to designate that interface as the configuration interface for a component.
+ +@Component(designate=Config.class)
+public class BasicComponent {
+ Config config;
+
+ @Activate void activate(Map<String,Object> props) {
+ config = Configurable.createConfig(props);
+ System.out.println("Hi " + config.message());
+ }
+
+ @Deactivate void deactivate() {
+ System.out.println("Bye " + config.message());
+ }
+}
+
This is an immediate component because it does not implement a service interface. It also requires a configuration because we have not specified this explicitly. When you use designate (or designateFactory) the default becomes require. This means that your component will only be created when there is actually configuration for it set.
+ +To run this component, make sure you have the Felix Webconsole running and the MetaType service installed. In the Webconsole, you can click on +‘'’Configuration’’’, your component should be listed on this page. By Clicking on the component with the name ‘'’Basic Component Config’’’ you get an editor window.
+ +The editor is aware of the proper types, it uses the [[MetaType]] standard to describe the properties. bnd uses the type information on the interface as well as the optional Metadata annotations to create a rich description that allows the web console to provide a good editor.
+ +You can fill in the message in the ‘‘Message’’ field. If you save the editor, your component prints the message with “Hi” in front of it. Deleting the configuration will print the message with “Bye”.
+ +If you change the message, you will see that the component is first deactivated and then reactivated again. This is the only possibility for the SCR because the component has not implemented a modified method. Adding the following method will change this, now changes to the configuration are signaled to the component and the component can continue to work. This is more complicated then recycling the component but it can create a more optimized system.
+ +@Modified
+void modified( Map<String,Object> props) {
+ // reuse activate method
+ activate(props);
+}
+
It is also possible to take advantage of the configuration factories. In this model
+ +An example, that implements a simple socket server on a configurable port and returns a message when a telnet session is opened to that port can be found on Github.
+ + +The OSGi Metatype specification provides a language to describe configuration information in an XML file. However, XML is cumbersome to use and eXtreMeLy error prone, and refactoring often finds it hard to change references in XML text files. Especially with DS components managing the relations can be complex, see [[Components]]
+ +For this reason, bnd provides annotations to define the XML based on a ‘‘configuration’’ interface. For example, the following interface defines a simple metatype:
+ +package com.acme; + import aQute.bnd.annotation.metatype.*;
+ +@Meta.OCD interface Config { + int port(); + }
+ +To turn this into an XML file, the bnd file must contain:
+ +-metatype: *
+ +The wild card will search through the whole bundle but it is possible to limit this to a restricted set of packages to speed up the processing. For example:
+ +-metatype: Metatype, com.libs.metatypes
+ +If the previous Config
interface is present in the output, the output will also contain an XML file at OSGI-INF/metatype/com.acme.Config.xml
. This file will look as follows:
bnd leverages the rich type information the Java class files contain. It inspects the returns types and from this it automatically calculates the AD type as well as the cardinality. For example, a method like:
+ +List
Provides an AD of:
+ +<AD
+ name='Ints'
+ id='ints'
+ cardinality='-2147483648'
+ required='true'
+ type='Integer'/>
+
bnd attempts to make the names of the OCD and AD human readable by un-camel-casing the id. This means that it uses the upper cases in the id to decide where to use spaces. It also attempts to replace ‘_’ characters with spaces and removes ‘$’. If the result is not what is wanted, the name can always be explicitly set with the AD.name() field.
+ +The id of the properties is by default derived from the method name. The name space of methods is restricted by the Java language both in character set as well as by the reserved keywords. Therefore, bnd mangles the name of the method to allow the method name to be adapted to common practices in proeprty names.
+ +$new
maps to the new
name. To make the name $x
, use a method with the name $$x
.a.b.c
comes from a_b_c
) and to indicate private properties, that is properties that start with a ‘.’. For example, _secret
maps to .secret
and this will be a property that is not registered as a service.For example:
+ +@OCD + interface Config { + String _password(); // .password - will not be registered as a service + String $new(); // new - keyword + }
+ +The OCD annotation is necessary to know that an interface is a Metatype interface. It should be used preferably without any parameters (they all have good defaults). However, each default is possible to override. The following table discusses the fields:
+ +name |
+ String | +A human readable name of the component, can be localized if it starts with a % sign. The default is a string derived from the id where _, $, or camel casing is used to provide spaces. | +
id |
+ String | +The id of the OCD, this will also be used for the pid of the Designate element. | +
localization |
+ String | +The localization id of the metatype. This refers to a properties file in the OSGI-INF/i10l/ |
+
description |
+ String | +A human readable description that can be localized. Default is empty. | +
factory |
+ String | +Will treat this OCD as intended to be for a factory configuration. The default is false |
+
The AD
is an optional annotation on methods of a OCD interface. The annotation makes it possible to override the defaults and provide extra information.
name |
+ String | +A human readable name of the attribute, can be localized if it starts with a % sign. The default is a string derived from the method name where _, $, or camel casing is used to provide spaces. | +
id |
+ String | +The id of the attribute. By default this is the name of the method | +
description |
+ String | +A human readable description that can be localized. Default is empty. | +
type |
+ String | +The type of the attribute. This must be one of the types defined in the Metatype specification. By default the type will be derived from the return type of the method. If no applicable type can be found then the String type is used as final default. |
+
cardinality |
+ int | +The cardinality of the attribute. If this is negative its absolute indicates maximum number of elements in a Vector. If it is positive it indicates the maximum number of values in an array. Zero indicates a scalar. If not provided, bnd will calculate the cardinality based on the return type. Collections will have a negative large value and arrays have a positive large value. | +
min |
+ String | +The minimum value allowed for this attribute. There is no default. | +
max |
+ String | +The maximum value allowed for this attribute. There is no default. | +
deflt |
+ String | +The default initial value. The default for this is an empty String | +
required |
+ boolean | +Indicates if this attribute is required. The default is that attributes are required. | +
optionLabels |
+ String[] | +Labels for any values specified in optionValues . The default value is unset if there are no optionValues are defined. If these are defined, then the labels are calculated from the values by making _, $ into spaces and providing spaces between camel cased words in the values. |
+
optionValues |
+ String[] | +Optional values. If this field is not set and the return type is an enum type then the values are calculated from the enum members. | +
The type support the OSGi Metatype specification is limited to the primitives and strings. So what happens when you use another type? During the build phase, bnd will revert to a ‘String’ Metatype type. This means that those special types are going to be strings in the dictionary. However, the aQute.bnd.annotation.metatype package contains a helper class that simplifies the usage of properties in runtime. It has a @createConfigurable(Class
For example:
+ +@Meta.OCD + interface Config { + URI[] uris(); + }
+ +public void updated( Map<String,Object> props) { + config = Configurable.createConfigurable(Config.class,props); + for ( URI URI : config.uris() ) { + … + } + }
+ +The Metatype specification does not support URIs, so how does this work? Lets first look at the AD:
+ +<AD
+ name='uris'
+ id='uris'
+ cardinality='2147483647'
+ required='true'
+ type='String'/>
+
Any editor will therefore put an array of strings (String[]) in the properties. When the proxy gets the string, it will therefore have to convert from the String[] -> URI[]. In this case, the URI has a String constructor and is used to do the conversion from String to URI. The converter can handle general array to collection, collection to array, and as indicated, any conversion to an object that has a String constructor.
+ +For convenience there are a number of built int conversions provided that cannot leverage the String constructor:
+ +valueOf
method to get an instance.The following example shows a very simple metatype configuration interface:
+ +package aQute.metatype.samples; + import aQute.bnd.annotation.metatype.Meta.*; + @OCD + public interface Config { + int port(); + }
+ +If the bnd.bnd file contains:
+ +-metatype: *
+ +Then bnd will detect this class as a Metatype and it generates the following XML in `OSGi-INF/metatype/aQute.metatype.samples.Config.xml
+ +<?xml version=’1.0’?>
+As usual, XML does an outstanding job in obfuscating the interesting parts. If you’re using the Apache Felix Webconsole (and if not, why not?) then you can edit this metatype on the web:
+ +%width=500px% https://www.aqute.biz/uploads/Bnd/webconsole.png
+ +This metatype can now be used in a simple example that prints the port number:
+ +package aQute.metatype.samples; + import java.util.; + import org.osgi.service.cm.; + import aQute.bnd.annotation.component.; + import aQute.bnd.annotation.metatype.;
+ +@Component(properties=”service.pid=aQute.metatype.samples.Config”) + public class Echo implements ManagedService {
+ +public void updated(Dictionary properties)
+ throws ConfigurationException {
+ if ( properties != null ) {
+ Config config = Configurable.createConfigurable(
+ Config.class, properties);
+ System.out.println(config.port());
+ }
+} }
+
The editor can get quite rich with the metatype information. For example:
+ +https://www.aqute.biz/uploads/Bnd/complex.png
+ +This information came from the following Meta interface:
+ +interface SampleConfig {
+ String _secret();
+ String $new();
+ String name();
+ enum X { A, B, C; }
+ X x();
+ int birthYear();
+ URI uri();
+ URI[] uris();
+ Collection
Though this is a big savings over normal fudging with properties, it gets better. The metatyping is fully integrated with DS. In this example, we’re using DS to register the Managed Service but this is not necessary because DS will automatically use the name of a component as the PID. So with a component life can be as easy as:
+ +@Component(designate=Config.class) + public class Echo2 { + @Activate + void activate(Map<?,?> properties) throws ConfigurationException { + Config config = Configurable.createConfigurable( + Config.class, properties); + System.out.println(config.port()); + } + }
+ +No more strings. Components and metatypes are extensively explained in [[Components]].
+ +The OSGi has a very elegant package version model there are still many that think this is too much work. They do not want to be bothered by the niceties of semantic versions and just want to use, let’s say, Servlet 3.0. For those people (seemingly not interested in minimizing dependencies) the OSGi Alliance came up with Portable Java Contracts. A contract allows you to:
+ +This very common pattern is called the Capability/Requirement (C/R) model in OSGi, it underlies all of its dependency concepts like Import/Export package and others; it forms the foundation of the OSGi Bundle Repository. If you ever want to know what is happening deep down inside a framework than look at the Wiring API and you see the requirements and capabilities in their most fundamental form. +You can find more information about the Portable Contracts in this blogpost.
+ +Capabilities declare a set of properties that describe something that a bundle can provide. A Requirement in a bundle has a filter that must match a capability before this bundle can be resolved. To prevent requirements matching completely unrelated capabilities they must both be defined in the same namespace,, where the namespace then defines the semantics of the properties. Using the C/R model we were able to describe most of the OSGi dependencies with surprisingly few additional concepts. For a modern OSGi resolver there is very little difference between the Import-Package and Require-Bundle headers.
+ +So how do those contracts work? Well, the bundle that provides the API for the contract has a contract capability. What this means is that it provides a Provide-Capability clause in the osgi.contract namespace, for example:
+ +Bundle P:
+ Provide-Capability:
+ osgi.contract;
+ osgi.contract=JavaServlet;
+ uses:="javax.servlet,javax.servlet.http";
+ version:Version="3.0"
+ Export-Package: javax.servlet, javax.servlet.http
+
This contract defines two properties, the contract name (by convention this is the namespace name as property key) and the version. A bundle that wants to rely on this API can add the following requirement to its manifest: +Bundle R:
+ + Require-Capability: osgi.contract;
+ filter:="(&(osgi.contract=JavaServlet)(version=3.0))"
+ Import-Package: javax.servlet, javax.servlet.http
+
Experienced OSGi users should have cringed at these versionless packages, cringing becomes a gut-reaction at the sight of versionless packages. However, in this case it actually cannot harm. The previous example will ensure that Bundle P will be the class loader for the Bundle R for packages javax.servlet, javax.servlet.http. The magic is in the uses: directive, if the Require-Capability in bundle R is resolved to the Provide-Capability in bundle P then bundle R must import these packages from bundle P.
+ +Obviously bnd has support for this (well, since today, i.e. version osgi:biz.aQute.bndlib@2.2.0.20130806-071947 or later). First bnd can make it easier to create the Provide Capability header since the involved packages are in the Export-Package as well as in the Provide-Capability headers. The do-no-repeat-yourself mantra dictated am ${exports} macro. The ${exports} macro is replaced by the exported packages of the bundle, for example: +Bundle P:
+ + Provide-Capability: \
+ osgi.contract;\
+ osgi.contract=JavaServlet;\
+ uses:="${exports}";\
+ version:Version="3.0"
+ Export-Package: javax.servlet, javax.servlet.http
+
That said, the most extensive help you get from bnd is for requiring contracts. Providing a contract is not so cumbersome, after all you’re the provider so you have all the knowledge and the interest in providing metadata. Consuming a contract is less interesting and it is much harder to get the metadata right. In a similar vein, bnd analyzes your classes to find out the dependencies to create the Import-Package statement, doing this by hand is really hard (as other OSGi developing environments can testify!).
+ +So to activate the use of contracts, add the -contract instruction:
+ +bnd.bnd:
+ -contract: *
+
This instruction will give bnd permission to scan the build path for contracts, i.e. Provide-Capability clauses in the osgi.contract namespace. These declared contracts cause a corresponding requirement in the bundle when the bundle imports packages listed in the uses clause. In the example with Bundle R, bnd will automatically insert the Require-Capability header and remove any versions on the imported packages. +Sometimes the wildcard for the -contract instruction can be used to limit the contracts that are considered. Sometimes you want a specific contract but not others. Other times you want to skip a specific contract. The following example skips the ‘JavaServlet’ contract: +bnd.bnd:
+ + -contract: !JavaServlet,*
+
The tests provide some examples for people that want to have a deeper understanding: https://github.com/bndtools/bnd/blob/next/biz.aQute.bndlib.tests/src/test/ContractTest.java Contracts will be part of the bnd(tools) 2.2 release (hopefully) at the end of this summer, until then they are experimental. Enjoy. Peter Kriens @pkriens Update: Last example to skip the ‘JavaServlet’ contract was reversed, updated the text to show a reverse example (anything BUT JavaServlet).
+ + +Manifest headers are challenging to keep in sync with the code in the bundle. It often takes several attempts to get all the details correct.
+ +One of the goals of bnd is to eliminate such issues by relying on Java’s type system to express the semantics of OSGi metadata.
+ +To address this bnd pioneered manifest annotations which evolved into OSGi’s bundle annotations. A bundle annotation is used to express metadata that cannot otherwise be derived from code.
+ +A bundle annotation is applied to a type or package and when processed by bnd will cause the generation of corresponding manifest headers (and header clauses). Generating manifest headers from type safe structures is far less likely to result in errors, simplifies the developers life and is more conducive to code refactoring which won’t result in information loss.
+ +The following example shows the preferred way to handle package versioning by applying the @Export
and @Version
bundle annotations to com/acme/package-info.java
.
@Export
+@Version("1.3.4")
+package com.acme;
+
which results in the manifest header:
+ +Export-Package: com.acme;version="1.3.4"
+
Though Java class files contain enough information to find code dependencies, there are many dependencies that are indirect. OSGi extenders for instance are often a requirement to make a bundle function correctly but often client bundles have no code dependency on the extender. For example, Declarative Services (DS) went out of its way to allow components to be Plain Old Java Objects (POJO). The result is that resolving a closure of bundles starting from a DS client bundle would not drag in the Service Component Runtime (SCR), resulting in a satisfied but rather idle closure.
+ +The solution was to describe the requirement for the runtime SCR dependency using Requirements and Capabilities. But again, writing these complex clauses in the manifest by hand is both error prone and painful.
+ +The @Requirement
and @Capability
annotations were designed to address this issue. These annotations can be used to create custom bundle annotations, described later on. Let’s discuss the DS example.
Recent DS specifications require implementations to provide the following capability:
+ +Provide-Capability: osgi.extender;
+ osgi.extender="osgi.component";
+ version:Version="1.4.0";
+ uses:="org.osgi.service.component"
+
While this provides a capability that can be required, we need a requirement to be generated from client code that uses DS. Enter recent versions of DS annotations which are meta-annotated with @RequireServiceComponentRuntime
, a custom bundle annotation which is specified as:
@Requirement(
+ namespace = ExtenderNamespace.EXTENDER_NAMESPACE,
+ name = ComponentConstants.COMPONENT_CAPABILITY_NAME,
+ version = ComponentConstants.COMPONENT_SPECIFICATION_VERSION)
+@Retention(RetentionPolicy.CLASS)
+public @interface RequireServiceComponentRuntime { }
+
If you inspect the source code for @Component
you’ll find it is meta-annotated with @RequireServiceComponentRuntime
. When you write a DS component using @Component
as follows
@Component
+class Foo { ... }
+
and because of the inherent bundle annotations it holds, the following manifest clause is generated
+ +Require-Capability: \
+ osgi.extender; \
+ filter:="(&(osgi.extender=osgi.component)(version>=1.4.0)(!(version>=2.0.0)))"
+
The invisible link created between user code and the indirect requirement is a powerful mechanism that enables automatic validation of a bundle closure.
+ +The actual requirement filter:
directive is constructed from an AND
of the filter()
, name()
, and version()
annotation methods. All fields are optional. The name field will create an assertion that the given namespace equals the value of the name()
annotation method. For example, if the namespace is com.example.foo
and the name()
method has the value bar
then the filter is (com.example.foo=bar)
. If a version is specified, it will be expanded to a filtered version-range expression. The convention of using the namespace name as the property key is commonly used in OSGi specification. For example, the filter (osgi.wiring.package=com.example.foo)
is the filter for an Import-Package com.example.foo
while osgi.wiring.package
is the namespace for the packages.
For example:
+ +@Requirement(namespace = "NAMESPACE", name="NAME", version="1.2.3", filter="(foo=${#foo})")
+@Retention(RetentionPolicy.CLASS)
+public @interface RequireSomething {
+ int foo();
+}
+
+@RequireSomething(foo=3)
+class Foo {...}
+
This will generate a manifest Require-Capability header of:
+ +Require-Capability: \
+ NAMESPACE; \
+ filter:="(&(foo=3)(NAMESPACE=NAME)(version>=1.2.3)(!(version>=2.0.0)))"
+
Bundle annotations aren’t just about package versioning or requirements and capabilities. They are about lifting metadata out of our code to avoid, among other things, error prone duplication of information. A common example is the bundle activator. Bundle Activators are require to be described in a manifest header. This association is not visible to refactoring tools and as such can easily end up out of sync.
+ +The @Header
annotation exists to address this problem.
package com.acme;
+
+@Header(name = Constants.BUNDLE_ACTIVATOR, value = "${@class}")
+public Activator implements BundleActivator { ... }
+
results in the manifest header:
+ +Bundle-Activator: com.acme.Activator
You’ll note the string ${@class}
used in the above example. String fields in bundle annotations are processed through bnd’s macro processor. This macro processor provides access to all default and builder macros. More info on bnd macros can be found in the macros chapter.
Bnd also provides access to certain key properties of the current processing state.
+ +The @Header
example above used the macro ${@class}
which lifted the @class
property holding the class name of the activator into the header to avoid having to duplicate it. This also means that refactoring the activator won’t cause the manifest to get out of sync.
In the case that a bundle annotation is used as a meta annotation then the methods on the annotated annotation are available as macros as well with a name prefixed with #
. That is, if the annotated annotation has a method foo()
, then the macro ${#foo}
can be used to refer to its value. See Accessor Properties for more details.
Certain bundle annotations have a second important use. We know that if applied to a type or package bundle annotations result in a clause in the manifest. However, many can be used as meta-annotations to a second annotation. The second annotation is considered a custom bundle annotation. The custom bundle annotation results in a manifest clause only when applied to a type or package.
+ +This makes it possible to create an annotation for a subsystem. For example, an annotation @ASL_2_0
that sets the Bundle-License
header to the Apache Software License version 2.0.
@BundleLicense(
+ name = "https://www.opensource.org/licenses/apache2.0.php",
+ link = "https://www.apache.org/licenses/LICENSE-2.0.html",
+ description = "Apache Software License 2.0")
+@interface ASL_2_0 {}
+
+// takes effect when applied to a type
+
+@ASL_2_0
+class Foo { ... }
+
When creating custom bundle annotations a common requirement is to make them parameterizable such that the values of the custom bundle annotation feed into the header clauses resulting from the bundle annotation applied to it (remember; a custom bundle annotation is meta-annotated with a bundle annotation.)
+ +OSGi specifies two annotations, @Attribute
and @Directive
, for this purpose. Any methods of the custom bundle annotation annotated with @Attribute
or @Directive
will result in those becoming additional attributes or directives respectively of the resulting header clause when a value is supplied.
@Attribute
@Attribute
allows you to add new or update existing attributes from the bundle annotation.
@Capability(namespace = "foo.namespace")
+@interface Extended {
+ @Attribute("foo.attribute") // this attribute enhances the @Capability
+ String value();
+}
+
+// usage
+
+@Extended("bar")
+class Foo {}
+
which results in the manifest header:
+ +Provide-Capability: foo.namespace;foo.attribute=bar
@Directive
@Directive
behaves similarly; with some caveats. You can add new or update existing directives for namespaces not defined by OSGi specifications.
@Capability(namespace = "foo.namespace")
+@interface Extended {
+ @Directive("foo.directive")
+ String value();
+}
+
+// usage
+
+@Extended("bar")
+class Foo {}
+
results in the manifest header:
+ +Provide-Capability: foo.namespace;foo.directive:=bar
However, namespaces defined by OSGi specifications will be validated and will not accept directives which are not part of the spec unless they are prefixed with x-
.
@Capability(namespace = "osgi.extender", name = "bar", version = "1.0.0")
+@interface Extended {
+ @Directive("foo")
+ String value();
+}
+
+// usage
+
+@Extended("bar")
+public class Foo {}
+
will result in an error:
+ +Unknown directive 'foo:' for namespace 'osgi.extender' in 'Provide-Capability'. Allowed directives are [effective:,uses:], and 'x-*'.
It should be noted that it’s possible to elide such errors using bnd’s -fixupmessages
instruction.
This next example however:
+ +@Capability(namespace = "osgi.extender", name = "bar", version = "1.0.0")
+@interface Extended {
+ @Directive("x-foo")
+ String value();
+}
+
+// usage
+
+@Extended("bar")
+public class Foo {}
+
results in the manifest header:
+ +Provide-Capability: osgi.extender;osgi.extender=bar;version:Version="1.0.0";x-foo:=bar
It should be noted that default values for methods annotated with @Attribute
and @Directive
are deemed to be for documentation purposes only and will not be emitted into resulting headers.
For more customisation options see chapter on Accessor Properties.
+ +OSGi bundle annotations can be found in the osgi.annotation
(e.g. org.osgi:osgi.annotation:7.0.0
) bundle.
<dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.annotation</artifactId>
+ <version>7.0.0</version>
+</dependency>
+
Bnd bundle annotations can be found in the biz.aQute.bnd.annotations
(e.g. biz.aQute.bnd:biz.aQute.bnd.annotation:5.0.0
) bundle.
<dependency>
+ <groupId>biz.aQute.bnd</groupId>
+ <artifactId>biz.aQute.bnd.annotation</artifactId>
+ <version>${bnd.version}</version>
+</dependency>
+
OSGi Bundle Annotations:
+ +@Attribute
@Capability
@Directive
@Export
@Header
@Requirement
@RequireConfigurationAdmin
@RequireMetaTypeExtender
@RequireMetaTypeImplementation
@RequireServiceComponentRuntime
@RequireEventAdmin
@RequireJPAExtender
@RequireHttpWhiteboard
@RequireConfigurator
@RequireJaxrsWhiteboard
@JSONRequired
@RequireCDIExtender
@RequireCDIImplementation
Bnd Bundle Annotations:
+ +@BundleCategory
– Sets the bundle category, existing categories are defined in an enum.@BundleContributors
– Creates an OSGi header for contributors that maps to the Maven contributors element.@BundleCopyright
– Sets the copyright header.@BundleDevelopers
– Creates an OSGi header for developers that maps to the Maven developers element.@BundleDocUrl
– Provides a documentation URL.@BundleLicense
- Creates entries in the Bundle-License
header.
+ @ASL_2_0
@BSD_2_Clause
@BSD_3_Clause
@CDDL_1_0
@CPL_1_0
@EPL_1_0
@GPL_2_0
@GPL_3_0
@LGPL_2_1
@MIT_1_0
@MPL_2_0
@ServiceConsumer
- Generates requirements in support of the consumer side of the Service Loader Mediator specification.@ServiceProvider
- Generates requirements and capabilities in support of the provider side of the Service Loader Mediator specification. Also generates META-INF/service
descriptors.When working with custom bundle annotations one may find that @Attribute
and @Directive
have limitations in scenarios where multiple bundle annotations are added to a single custom bundle annotation because there are no discriminators expressing which bundle annotation to associate the attribute or directive; and it’s likely that associating these with all bundle annotations produces unexpected or even incorrect results. Bnd provides a solution which takes advantage of its macro support during annotation processing.
When an annotation is meta-annotated with a bundle annotation the values and defaults of the host are loaded as properties, prefixed with #
, into the macro processing scope. The effect is that these values are accessible using a macro pattern (like ${}
).
@Capability(
+ name = "${#name}", // <-- accesses the hosts 'name()' value
+ namespace = "osgi.cdi.extension",
+ version = "${#version}" // <-- accesses the hosts 'version()' value
+)
+@Requirement(
+ name = "osgi.cdi",
+ namespace = "osgi.implementation",
+ version = "1.0.0")
+@interface CDIExtension {
+ String name();
+ String version();
+}
+
Annotation methods may return several different types and they are converted to strings according to the following table:
+ +Return type | +Conversion | +
---|---|
boolean , byte , char , double , float , int , long , short |
+ String.valueOf() |
+
java.lang.Class |
+ Class.getName() |
+
java.lang.Enum |
+ Enum.name() |
+
java.lang.String |
+ as is (commas should be escaped with \ ) |
+
array type whose component type is one of the preceding types | +joined by comma (, ) |
+
type satisfying Class.isAnnotation() |
+ omitted from available accessor properties | +
array type whose component type satisfies Class.isAnnotation() |
+ omitted from available accessor properties | +
It should be noted that if the resulting strings contain macro characters these will be interpreted by bnd’s macro engine. This may produce unexpected results due to potentially nested macros. In this scenario steps must be taken to either escape these characters or that nesting is done to allow bnd to successfully process the result.
+ +The return type Class<?>
is particularly useful when it comes to making your code friendly to refactoring.
@Capability(
+ filter = "objectClass:List<String>='${#value}'",
+ namespace = "osgi.service",
+)
+@interface MyService {
+ Class<?>[] value();
+}
+
+// applied as
+
+@MyService(Bar.class)
+class Bar { ... }
+
What if you want the default to be the annotated class itself? Bnd made this possible by using a type which was unlikely to be a real argument. The type selected was java.lang.annotation.Target
because it is guaranteed to be accessible, is unlikely to be a real argument, will not require an additional dependency, and it’s nomenclature proved the most expressive for the use case.
@Capability(
+ filter = "objectClass:List<String>='${#value}'",
+ namespace = "osgi.service",
+)
+@interface MyService {
+ Class<?>[] value() default Target.class;
+}
+
+// applied as
+
+@MyService
+class Bar { ... }
+
Instances of java.lang.annotation.Target.class
found in the return value of Class<?>
or Class<?>[]
methods will be replaced by the annotated type prior to string conversion.
In order to control the cardinality of requirements generated by custom bundle annotations a convenience enum aQute.bnd.annotation.Cardinality
is available as a method return type. The default value should be Cardinality.DEFAULT
ensuring that if not set no directive will be emitted.
@Requirement(
+ attribute = {
+ aQute.bnd.annotation.Constants.CARDINALITY_MACRO
+ },
+ namespace = "osgi.service",
+)
+@interface RequireMyService {
+ Cardinality cardinality() default Cardinality.DEFAULT;
+}
+
+// applied as
+
+@RequireMyService(cardinality = Cardinality.MULTIPLE)
+class Bar { ... }
+
Note: It is recommended to use the macro constant aQute.bnd.annotation.Constants.CARDINALITY_MACRO
crafted specifically for handling the cardinality directive. It should also be noted that during macro processing the enum name()
will be converted to lower case.
In order to control the resolution of requirements generated by custom bundle annotations a convenience enum aQute.bnd.annotation.Resolution
is available as method return type. The default value should be Resolution.DEFAULT
ensuring that if not set no directive will be emitted.
@Requirement(
+ attribute = {
+ aQute.bnd.annotation.Constants.RESOLUTION_MACRO
+ },
+ namespace = "osgi.service",
+)
+@interface RequireMyService {
+ Resolution resolution() default Resolution.DEFAULT;
+}
+
+// applied as
+
+@RequireMyService(resolution = Resolution.OPTIONAL)
+class Bar { ... }
+
Note: It is recommended to use the macro constant aQute.bnd.annotation.Constants.RESOLUTION_MACRO
crafted specifically for handling the resolution directive. It should also be noted that during macro processing the enum name()
will be converted to lower case.
In order to control the effective time of a requirement generated by custom bundle annotations a method called effective
with a return type of String
can be used in conjunction with the macro constant aQute.bnd.annotation.Constants.EFFECTIVE_MACRO
. The default value should be the empty string (""
) ensuring that if not set no directive will be emitted.
@Requirement(
+ attribute = {
+ aQute.bnd.annotation.Constants.EFFECTIVE_MACRO
+ },
+ namespace = "osgi.service",
+)
+@interface RequireMyService {
+ String effective() default "";
+}
+
+// applied as
+
+@RequireMyService(effective = "active")
+class Bar { ... }
+
In order to control the uses constraints of a requirement generated by custom bundle annotations a method called uses
with a return type of Class<?>[]
can be used in conjunction with the macro constant aQute.bnd.annotation.Constants.USES_MACRO
. The default value should be an empty array ({}
) ensuring that if not set the directive will not be emitted.
@Requirement(
+ attribute = {
+ aQute.bnd.annotation.Constants.USES_MACRO
+ },
+ namespace = "osgi.service",
+)
+@interface RequireMyService {
+ Class<?>[] uses() default {};
+}
+
+// applied as
+
+@RequireMyService(uses = {com.acme.Foo.class, org.bar.Bar.class})
+class Bar { ... }
+
####
+ +Writing Java libraries which support OSGi does not typically require more than generating proper OSGi metadata. bnd helps accomplish with minimal effort on the part of developers. One issue that remains somewhat complex is properly handling the use of the Java SPI such that it works seamlessly in OSGi and avoiding the need for custom code to accomplish a similar goal. OSGi defines the Service Loader Mediator Specification but applying it requires a significant amount of manifest metadata.
+ +bnd defines a set of SPI annotations which provide a simple solution for developers who wish to maintain OSGi friendly libraries.
+ +The Java SPI use cases are broken into two groups; providers and consumers. The @aQute.bnd.annotation.spi.ServiceConsumer
annotation is used in consumer code to express a requirement on an SPI service type adding the appropriate OSGi metadata to the manifest.
@ServiceConsumer(
+ value = JsonProvider.class
+)
+public abstract class JsonProvider {
+ public static JsonProvider provider() {
+ for (JsonProvider provider : ServiceLoader.load(JsonProvider.class)) {
+ return provider;
+ }
+ throw new JsonException("provider not found");
+ }
+ ...
+}
+
@ServiceConsumer
also grants the developer control in defining many facets of the generated OSGi requirements such as cardinality
, effective
time and resolution
.
The provider side is supported by the @aQute.bnd.annotation.spi.ServiceProvider
annotation. It is used to express a capability for a given SPI service type adding all the appropriate OSGi manifest metadata.
@ServiceProvider(JsonProvider.class)
+public class JsonProviderImpl extends JsonProvider {
+ ...
+}
+
@ServiceProvider
also grants the developer control in defining many facets of the generated OSGi requirements and capabilities such as additional attributes and directives (attributes becoming service properties), cardinality
, effective
time, resolution
, and package uses
.
The @ServiceProvider
annotation will automatically result in publishing OSGi services for each provider discovered. Any attributes (excluding directives) specified on the annotation will be automatically added as OSGi service properties.
@ServiceProvider(
+ value = JsonProvider.class,
+ attribute = {
+ "colors:List<String>='blue,green,red'"
+ }
+)
+public class JsonProviderImpl extends JsonProvider {
+ ...
+}
+
An osgi.service
capability is also generated for each provider type.
An additional feature provided by bnd is the ability to manage the service descriptor files (a.k.a. META-INF/services/*
) automatically for any osgi.serviceloader
capabilities it finds having the register:
directive containing a provider’s fully qualified class name. The register:
directive is automatically generated from all instances of @ServiceProvider
.
This Application Note is about resolving in OSGi. The OSGi Framework has always used a resolver to wire a given set of bundles together, ensuring that only valid wires are made. However, the same OSGi resolver can also be used to select a set of bundles from a much larger set. This application note discusses this secondary usage.
+ +The resolver model is based on technology developed in OSGi since 2006 with RFC-0112 Bundle Repository. This RFC laid out a model that was gradually implemented in the OSGi specifications and for which many tools were developed. Resolving automates a task that is mostly done manually today.
+ +The pain that the resolver solves is well known to anybody that builds application from modules, the so called assembly process. All developers know the dreadful feeling of having to drag in more and more dependencies to get rid of Class Not Found errors when the application starts (or after running for an hour). For many, Maven was a significant improvement because it made this process so much easier because dependencies in Maven are transitive. If you depend on some artefact, then this artefact drags in its own dependencies to the runtime.
+ +Unfortunately, the Maven dependency model has limitations for a number of reasons. Though it undeniably has become easier to get a result, the result is often littered with unnecessary or wrong artefacts. Often application developers have no real understanding what is in their runtime.
+ +These are the causes:
+ +The result is that all too often the class path of the final application is littered with never used byte codes and/or overlapping packages that came from artefacts with different versions. If the customer could see the mess the average class path is they would run away screaming.
+ +Establishing the perfect class path for an application is a process for which the human mind is extremely badly suited and the transitive model of Maven has too many sharp edges. The resolver provides an alternative model that focuses on looking at the whole solution space instead of having to live with the (sometimes arbitrary) decisions of developers or of deeply nested transitive dependencies.
+ +The design of the resolving model is quite simple. It consists of the following entities:
+ +String
, Integer
, Double
, and Long
.Resolving in this App note is therefore the process of constructing an application out of resources. Resolving takes a list of initial requirements, a description of the system capabilities, and one or more repositories. It will use the list of initial requirements to find resources in the repository that provide the required capabilities. Clearly, these resources have their own requirements, retrieving applicable resources is therefore a recursive process. A resolver will find either a solution consisting of a set of resources where all requirements are satisfied or that there is no solution.
+ +It is important to realise that a resource and its capabilities and requirements are descriptions. They provide a formal representation of an external artefact. Since these formal representations can be read by a computer, we can calculate a closure of resources that, when installed together, have only resources where all their mandatory requirements are satisfied by the resources in the closure.
+ +Although the OSGi specifications started out with a set of headers that each had their own semantics, over time the specification migrated fully to the more simple and formal model of Resources, Capabilities, and Requirements. Since the function of the legacy headers were still needed, it was necessary to map these legacy headers to the formal model. This resulted in a number of OSGi core namespaces.
+ +osgi.wiring.identity
– Bundle-SymbolicName
header.osgi.wiring.bundle
– Bundle-SymbolicName
and Require-Bundle
header.osgi.wiring.package
– Import-Package
and Export-Package
headers.osgi.wiring.host
– Bundle-SymbolicName
and Fragment-Host
header.osgi.ee
– Bundle-RequiredExecutionEnvironment
header.osgi.native
– Bundle-NativeCode
header.osgi.content
– Provides the URL and checksum to download the corresponding artefact. (This namespace is defined in the compendium Repository specification.)Each namespace defines the names of the properties and their semantics. For the OSGi namespaces, there are classes like org.osgi.framework.namespace.IdentityNamespace
that contain the details of a namespace.
To make the model more clear let’s take a closer look to a simple bundle com.example.bundle
that exports package com.example.pe
and imports a package com.example.pi
. When the bundle is installed it will require that some other bundle, or the framework, provides package com.example.pi
. We can describe this bundle then as follows:
Resource for bundle com.example.bundle
+ capabilities:
+ osgi.wiring.identity; osgi.wiring.identity=com.example.bundle
+ osgi.wiring.bundle; osgi.wiring.bundle=com.example.bundle
+ osgi.wiring.host; osgi.wiring.host=com.example.bundle
+ osgi.wiring.package; osgi.wiring.package=com.example.pe
+ requirements:
+ osgi.ee; filter:="(&(osgi.ee=JavaSE)(version=1.8))"
+ osgi.wiring.package; filter:='(osgi.wiring.package=com.example.pi)'
+
Clearly, an Export-Package: com.example.pe
is a bit easier to read than the corresponding capability, let alone the filter in the requirement. This is especially true when the versions are taken into account, with the assertion of the version range the filters become quite unreadable. Clearly, if this had to be maintained by human developers then a model like Maven would be far superior. Fortunately, almost all of the metadata that is needed to make this work does not require much extra work from the developer. For example, the bnd
tool that is available in Maven, Gradle, Eclipse, Intellij and others can easily analyse a JAR file and automatically generate the requirements and capabilities based on the source code.
In certain cases it is necessary to provide requirements and capabilities that bnd cannot infer. However, Manifest annotations support in bnd can be used to define an annotation that will add parameterised requirements to the resource.
+ +Really, when you use an adequately appointed toolchain (like Bndtools) none of this gibberish is visible to normal developers unless they have a special case. The gibberish is left to the tools that prefer gibberish over natural language.
+ +For a bundle to be a good citizen in a resolution it suffices to follow the standard rules of good software engineering. However, since there is so much software out there that does not follow these rules, a short summary.
+ +java.*
which is an implicit import). Therefore, a developer must always be aware of this trade off.To see the requirements and capabilities of a bundle Bndtools has a special Resolution
view. This shows the requirements and capabilities of a selected JAR file or a bnd.bnd file.
This view uses a number of icons to represent the requirements/capabilities. You can hover over them to see further details.
+ +osgi.service
.osgi.identity
osgi.wiring.package
osgi.ee
osgi.extender
osgi.bundle
Developers that are keen on developing good bundles should pay close attention to this pane.
+ +The previous diagram adds the resolver to the earlier diagram of the basic model. It adds the following entities:
+ +An important variable in the resolving process is effective
which defines the effectiveness under which the resolve operation is performed. The resolver will only look at requirements that it deems effective. The default effectiveness is resolve
. The effectiveness active
is a convention commonly used for situations that do not need to be resolved by the OSGi framework but are relevant in using the resolver for assembling applications.
A repository is a collection of resources. This could be Maven Central or it could be a single bundle. That said, neither is a good idea in practice. The repository is the scope of the resolution. Only resources in the repository can be selected. Although the naive idea is to make the scope as large as possible to let the computer do the work, it is much better to curate the repository.
+ +There are many reasons why you need a curated repository but basically it comes down is the GIGO principle: garbage is garbage out. Another reason is running time. The resolver gets overwhelmed quickly when there are too many alternatives for a requirement. Resolving is an NP complete problem and this means that the resolution time quickly becomes very long when a lot of alternatives need to be examined.
+ +The bnd toolchain provides OSGi Repository access for many popular repository formats.
+ +pom.xml
. It integrates fully with the local ~/.m2
repository.Since bnd has an extensive library to parse bundles and generate the resources it is relatively easy to parse bundles in other ways. For example, there is a Maven plugin that can generate an OSGi index.
+ +A repository generally represents a release of a software product. Many developers publish an OSGi Repository XML to allow the resolver access to the metadata of their bundles, for example Knopflerfish produces an OSGi XML Repository with all their bundles.
+ +This model is in contrast with Maven Central and most other Maven repositories. These repositories are designed to contain everything that was ever released. An OSGi repository is more the content of a specific release. Since it generally only contains a specific release, it will disallow the resolver to use any unwanted resources.
+ +A natural repository is the bnd workspace since a workspace is a collection of related projects. Since these projects are build together it is trivial to keep them compatible. The metadata burden can be mitigated by sharing metadata between these related projects. For example, in bnd all bundles share the same version. Although this might sometimes release unchanged bundles under a newer version, the only cost is a bit of disk space. A small price to pay for an otherwise very error prone manual process. The Gradle build can release to a Nexus repository and automatically generate an index that can be used as repository.
+ +By far the best way to get experience with the resolver is using the bnd’s bndrun
files. Bndtools provides a friendly user interface that makes it easy to use the OSGi Resolver in an interactive way.
A bndrun
describes the runtime configuration for an application. This can either be a standalone executable JAR or an application that should be hosted in a container like a Java EE server or Karaf. In this app note, the target is an executable JAR unless otherwise noted.
If you double click on the bndrun file, it opens a Run pane.
+ +>
+ +Even though most lists are hidden, the pane is already quite overwhelming. So let’s go through the GUI and explain it one by one.
+ +Remember that a resolution is for a specific system, in our case an executable JAR. In the Core Runtime
pane we specify the OSGi framework that will be used as well as the execution environment. The execution environment is from a list of Java VM versions and OSGi specifications.
The execution environment is used to calculate the system capabilities. That is, the system is treated as a single resource that provides the capabilities of the Framework as well as the capabilities of the OSGi defined execution environments. The system resource is always included in the resolution but can of course never be downloaded, it is the target environment.
+ +The Browse Repos
is a list of the resources that are found in the active repositories. A search field makes it easy to find specific resources. For example, if you type in gogo
it will list all Gogo bundles.
One or more resources from the Browse Repos
list can be selected and then dragged to the Run Requirements
list to the left. This adds an identity requirement to the set of initial requirements. You can also drag a resource to the Run Blacklist
and Run Bundles
lists.
This is the main list to watch. It contains the set of initial requirements given to the resolver. The GUI makes it possible to add identity requirements and remove listed requirements. The easiest way is to use drag and drop form the Browse Repos
list but it is also possible to use the green +
, which opens a dialog from which bundles can be added directly from the selected repositories.
There are some more panes that are useful but they will be handled in diagnosing problems. For reference, a short introduction to these panes.
+ +bndrun
file to standalone. A standalone bndrun
file has no relation to the workspace it resides in and establishes its own repositories.Run
, Debug
, and Export
functions always work from the -runbundles
list.Taking the initial requirement it is possible to resolve by clicking on the Resolve
button. This will show a rather large dialog window with the resolution.
This dialog window is divided in three main parts:
+ +If some of the listed optional resources are desired then they can be selected. Pressing the Update and Resolve
button will restart the resolver but now with the selected optional resources as mandatory.
If the Finish
button is pressed then the current required resources list is converted to the list of Run Bundles
. You can inspect them at the right bottom of the bndrun
editor window.
After a successful resolve you can either Run
, Debug
, or Export
the bndrun
file.
So far the ideal process of happy resolves and satisfied bundles has been described. It is now necessary to leave this rosy world and descend into the world of failed resolves. Unfortunately, the provided diagnostic information when a resolve fails is quite low.
+ +When a resolve fails it returns a cause but more often than not this is not the real cause. This is not some shortcoming from the current resolver but a fundamental logical problem. The simplest form of a resolution is if you have for example 3 numbers 1,4,8. You need to find the numbers that sum to 10 using only addition and subtraction. If you try out all the combination then you find that no combination works. A failure report could be that -3 is not available because the last tried permutation was 1+4+8. Clearly, before that permutation many other numbers were missing as well, the missing -3 just happened to be the last one …
+ +That said, there are a number of scenarios where the resolver does give a hint where the problem is.
+ +This rather obscure message indicates that the resolver tries to include a api bundle that was made unresolvable.
+ +The api bundle may have a special Require-Capability header such as:
+ + Require-Capability: \
+ compile-only
+
This header creates a requirement that cannot be satisfied. There is nothing special with compile-only
, it is just an unused namespace. It could also have been foo-bar
.
In the resolver, you will see the following error chain:
+ + Unable to resolve <<INITIAL>> version=null:
+ missing requirement osgi.enroute.examples.resolver.missingapi.provider
+ -> Unable to resolve osgi.enroute.examples.resolver.missingapi.provider version=1.0.0.201710041250:
+ missing requirement osgi.enroute.examples.resolver.missingapi.api; version=[1.0.0,1.1.0)
+ -> Unable to resolve osgi.enroute.examples.resolver.missingapi.api version=1.0.0.201710041249:
+ missing requirement false]]
+
Note: Unfortunately, the output is blurred by a misguided attempt to make the output more concise. Because of this, the distinction between a bundle and a package is not very clear. Sadly you can only see the difference between a requirement for a bundle and a package by looking at the version. If this is a range then it is a package and if it is a version with a timestamp it is a bundle. (This works most of the time.) This is a bug in bnd and should be corrected.
+ +As indicated, you really need to understand that the diagnostic is just the last path the resolver took. The osgi.enroute.examples.resolver.missingapi.provider
bundle tries to find a provider for the package osgi.enroute.examples.resolver.missingapi.api
and has found the osgi.enroute.examples.resolver.missingapi.api
bundle. However, this API bundle has the impossible to satisfy compile-only
requirement.
That said, it is better to look at the Missing Requirements
list since this reports quite nicely what is missing.
The icon and the text more clearly indicate that it cannot resolve the osgi.enroute.examples.resolver.missingapi.api
bundle due to the compile-only
requirement.
Exporting the API package from the provider bundle will correct this case.
+ +Many developers compile against the compendium bundle to get the OSGi service API packages. Although compiling against an API bundle has advantages, using the compendium bundle in runtime is evil. Since the compendium bundle aggregates a large number of API packages it will have the tendency to unnecessarily constrain the versions of different APIs. That is, it blocks you from using newer APIs.
+ +To make bundles that should not be used at runtime not resolvable there is the Run Blacklist
list on the bndrun
editor. This list contains bundles that should never be included in a resolution.
For example, we create a bundle that implements the Wire Admin service.
+ + public class MyService implements WireAdmin {
+ ...
+ }
+
This will cause a requirement for an exported package org.osgi.service.wireadmin
. We therefore add the OSGi compendium bundle to the -buildpath
. This then compiles fine.
However, the resolve will then drag in the OSGi compendium bundle.
+ + + +If we look at the reasons when we select the compendium bundle we see that also the Configuration Admin imports from the compendium even though it actually might provide a higher version. (This maybe understandable but a really bad practice.)
+ + + +To get rid of the compendium bundle we can drag and drop it to the Run Blacklist
window. Any requirement in the Run Blacklist
list will automatically exclude all bundles that are selected by that requirement.
Sometimes the resolver can complain about a missing requirement but you are sure that it is in the repository. The first thing is to try to isolate the problem. Almost any problem can be solved if you remove the redundant parts. Quite often developers are trying to debug this situation in a complex large bndrun
file and then get overwhelmed.
Just create a new bndrun
file and only add the bundle you think should provide the resource to the Run Requires
list, keep only one requirement, and then resolve. If this resolves fine then at least you know that that bundle can potentially resolve.
However, often you find that even on its own it does not resolve. In most case the error message and the Reasons
list provide sufficient information to understand why it does not resolve.
It is still a mystery, try checking the Run Blacklist
list. If it is not there, it might be time to raise a bug.
Another tool for diagnosing potential issues in your OSGI framework (bundle, packages, services), the SCR info, configuration, log, and custom extensions is the OSGi bnd Snapshot Viewer.
+ +So far this App Note only visited the graphic user interface (GUI). However, bnd always keeps all information in simple properties files that can also edited as text. In the Run editor (that edits bndrun
files) you can also select the Source
view. Not all features of a bndrun
file can be manipulated through the GUI. This section therefore shows what is in the source and it can be manipulated.
Notice that most instructions are merge properties. That is, bnd will first find all properties that start with the instruction name and merge their values together. For example, if you set -runrequires
, -runrequires.foo
, and -runrequires.bar
bnd will use the combination of these properties. The order is the sorting order of the names used.
-distro
option, it contains additional capabilities that are not in the distro files.The problem with metadata is that it can also be wrong. Especially legacy bundles lack the proper metadata to inform the resolver that they provide a service or require an implementation of a specification. This is not a problem in runtime since these requirements are generally not used by the Framework, they are designed for using the resolver in selecting a closure of bundles. However, in certain cases adding a capability or a requirement to a bundle in the repository would simplify things. Clearly it is possible to wrap the legacy bundle but this is extremely cumbersome and can create confusion down the line.
+ +Therefore, another method is to use the augments that the bnd repositories support. Augments can add capabilities and requirements to existing bundles in a repository. However, it can only do this for the interactive resolve process. When the OSGi Framework resolves a number of bundles it will never takes augments into account.
+ +There are two different ways to add the augments.
+ +bndrun
file. This is an ad-hoc mechanism that is normally a last resort. For non-standalone bndrun files you can also add augments in the cnf/ext
directory since any bnd file in there will add its properties to the bndrun file. It is of course also possible to include file in any bnd
/bndrun
file.bnd.augment
capability. Such a resource has a file with properties that describe the augment for that repository. This is a good way when you have to curate a repository and need to fixup some legacy bundles. This resource can also add to the blacklist.In both cases the augments are described using the standard OSGi/bnd syntax. The syntax is not a beauty since it stretches what you can do with the OSGi header format. However, augmenting should really be a last resort so maybe it is not really bad that the syntax is cumbersome.
+ +You can add an augment with the -augment
instruction in the bndrun
file.
-augment PARAMETER ( ',' PARAMETER ) *
+
Augmenting is adding additional capabilities and requirements. When bnd resolves a project or bndrun file, it will read these instructions. Since -augment
is a merge property you can also use additional keys like -augment.xyz
.
The key
of the PARAMETER
is used for matching the Bundle Symbolic Name. It can contain the *
wildcard character to match multiple bundles. The bundle symbolic name must be allowed as a value in a filter it is therefore not a globbing expression.
The following directives and attribute are architected:
+ +[<version>,∞)
. The version range can be prefixed with an @
for a consumer range (to the next major) or a provider range (to the next minor) when the @
is a suffix of the version. The range can restrict the augmentation to a limited set of bundles.capability:
directive specifies a Provide-Capability instruction, this will therefore likely have to be quoted to not confuse bnd with embedded comma’s. Any number of clauses can be specified in a capability directive by separating the clauses with a comma. (This is where the syntax gets stretched.)requirement:
directive specifies a Require-Capability instruction similar to the capability:
directive.To augment the repositories during a resolve, bnd will find all bundles that match the bundle symbolic name and fall within the defined range. If no range is given, only the bundle symbolic name will constrain the search. Each found bundle will then be decorated with the capabilities and requirements defined in the capability:
and requirement:
directive.
For example, we need to provide an extender capability to a bundle with the bundle symbolic name com.example.prime
with version [1.2,1.3)
. In that case add the following instruction to the bndrun
file.
-augment.prime = \
+ com.example.prime; \
+ capability:='osgi.extender; \
+ osgi.extender=some.extender; \
+ version:Version=1.2@'
+
The capability:
and requirement:
directives follow all the rules of the Provide-Capability and Require-Capability headers respectively. For the resolver, it is as if these headers were specified in their manifests. Since these headers can contain semicolons and commas they must be quoted. bnd will allow double quotes inside normal quotes and vice versa when it is necessary to nest quotes.
A workspace setup with bnd will generally provide a good start. However, when you need to grandfather in a lot of bundles from Maven Central then it is likely that you will need to spend some time to augment these bundles. This can be a depressing task since you’ll find out how messy the world is. However, experience shows that once the repository is resolvable, maintaining it has very little overhead. Better, it tends to signal probems very early in the development process.
+ +There are a number of (rudimentary) functions in the command line version of bnd that might be useful. Unfortunately, the commands currently assume an OSGi repository.
+ +With openliberty, WebSphere Liberty, Karaf, Liferay, etc. you are deploying into an existing container which already has a lot of capabilities. The crux of the issue becomes resolving only what you need to deploy. What you need at that point is a way to find out what the container already provides in a format which can be used during resolve time.
+ +Currently, the way to do that is to create a distro of the target container. This distro is a JAR file which provides all the capabilities that the target container provides at one point in time. It includes the capabilities of all currently installed bundles. It also contains all capabilities provided by the system bundle which may have been configured by framework properties. It is an aggregate view of all the capabilities available in the framework contained in a single JAR.
+ +Execute the following command using the bnd cli:
+ + bnd remote distro -o container-5.6.7.jar container 5.6.7
+
container-5.6.7.jar
created in 2. and place it into the directory containing the bndrun file that is used to resolve your deployment jars.in the bndrun file add:
+ + -distro: ${.}/container-5.6.7.jar;version=file
+
What you do with those resolved bundles is dependent on the goal of the developer. They can be directly installed in the existing container, or they could be assembled into a package format native to the container, or possibly a subsystem.
+ +What you need to bear in mind is that the distro needs to be re-created each time the target container changes in any significant way, otherwise it won’t reflect the true capabilities of the system needed to resolve against.
+ +Creating applications from reusable models is a goal that the software industry has been trying to achieve for a long time. To a certain extent, the Maven dependency model provides this model. However, it also is a model where dependencies are not strictly managed and much is left to chance.
+ +The resolver model provides an alternative (working inside maven if so desired) to establish a class path that optimises the whole application class path instead of just slavishly following transitively dependencies. Experience shows that organisations that use the resolver have a much better grip of what is actually running in their runtime. Not only minimises this runtime errors, it generally also makes it easier to migrate and evolve the code base.
+ +Converting an existing build into a resolve based build can be daunting but the efforts are worth it. For bnd users that use the workspace model the advantages will flow freely.
+ + +bnd integrates an OSGi launcher. This launcher will start a framework and then install and start a list of bundles. Launch descriptions are defined in a bndrun file. (A bnd.bnd file can actually also act as a bndrun file.) The bndrun file inherits properties from the workspace, not the profile.
+ +The launching environment is described with a number of instructions that start with -run
.
-runfw
— Specify the run framework in repository format-runsystemcapabilities
— Capabilities added to the environment-runbundles
— A list of bundles to install in repository format-runvm
— VM options. Use a comma to separate different options. E.g. -Dx=4, -Dy=5
.-runproperties
— System properties for the framework to create-runpath
— A list of jars (not required to be bundles) that are put on the classpath and available from the system bundle. Any Export-Package and Provide-Capabilityheaders are provided as packages/capabilities from the framework. The -runpath
can also override the default launcher.-runsystempackages
— An Export-Package list for packages exported by the system bundle.-runkeep
– Keep the framework working directory. That is, do not clean at start up-runstorage
– The working directory-runnoreferences
– Do not use the reference:
scheme when installing. (Sometimes required on Windows).Additional properties can be specified, and can be inherited from the workspace, that are specific for a launcher or are used for exporting a bndrun to an executable format like OSGi Subsystems, KARs, WARs, or executable JARs.
+ +Launchers are not build into bnd, the actual launching strategy is parameterized. A launcher is associated with a bnd or bndrun file by placing a JAR on the -runpath
. A JAR should have a Launcher-Plugin
header to be a launcher. If no launcher is found on the -runpath
then the built-in biz.aQute.launcher
will be used.
The plugin maps the bnd model specified in a bndrun or bnd file to an external execution. In general this plugin launches or contacts a VM, installs the -runpath
, installs -runbundles
, fires up the bundles. The plugin is also called when the bundles or settings have changed so that it can dynamically update the bundles on the running VM.
There are currently two launchers in the bnd repository:
+ +biz.aQute.launcher
– Default launcher. This launcher starts a VM on the local machine and keeps it synchronized with the changes in the IDE. The launcher can also create an executable JAR.biz.aQute.remote.launcher
– A launcher that can communicate with a remote VM, optionally installs a -runpath
(among which a framework), and then synchronizes the -runbundles
and a number of other properties with the remote VM.The default launcher in bnd. It creates a new VM with the given options, creates a framework using the OSGi launching API, and then manages the bundles on this framework with the OSGi launching API. It can update the remote framework in real time by changing a properties file that is watched for by the launcher class running in the remote framework.
+ +-runfw: org.apache.felix.framework;version='[4,5)'
+-runbundles: \
+ org.apache.felix.shell,\
+ org.apache.felix.shell.tui,\
+ org.apache.felix.scr,\
+ org.apache.felix.http.jetty,\
+ org.apache.felix.configadmin,\
+ org.apache.felix.metatype,\
+ org.apache.felix.log,\
+ org.apache.felix.webconsole,\
+ osgi.cmpn,\
+ aQute.xray.badbundle;version=latest,\
+ aQute.xray.plugin;version=latest,\
+ aQute.xray.hello;version=latest,\
+ com.springsource.org.apache.commons.fileupload;version=1.2.1,\
+ com.springsource.org.apache.commons.io;version=1.4.0,\
+ com.springsource.org.json;version=1.0.0
+
+-runproperties: org.osgi.service.http.port=8080
+
+-runrequires:\
+ bundle:(symbolicname=org.apache.felix.shell),\
+ bundle:(symbolicname=org.apache.felix.shell.tui),\
+ bundle:(symbolicname=org.apache.felix.webconsole),\
+ bundle:(symbolicname=org.apache.felix.configadmin),\
+ bundle:(symbolicname=org.apache.felix.metatype),\
+ bundle:(symbolicname=org.apache.felix.log),\
+ bundle:(&(symbolicname=osgi.cmpn)(version>=4.2)),\
+ bundle:(&(symbolicname=org.apache.felix.scr)(version>=1.6.0))
+
The launcher analyzes the -runpath
JARs. Any additional capabilities in the manifest (packages and Provide Capability headers) in these JARs are automatically added to the framework.
The launcher registers a service with object class Object
that provides some runtime information. The following properties are set on this service:
launcher.arguments
– The arguments passed to the main
method.launcher.properties
– The properties handed to the launcher. These properties are modified by any overriding properties that were set from the command line.launcher.ready
– A boolean set to true, indicating the launcher has done all its work.service.ranking
– Set to -1000The launcher supports embedded activators. These are like normal Bundle Actviator classes but are instead found on the -runpath
. Any bundle that has the header Embedded-Activator
will be started. The start can happen at 3 points that are identified with a static field in the Embedded Activator class. This field is called IMMEDIATE
. For example:
public class MyEmbeddedActivator implements BundleActivator {
+ public static String IMMEDIATE = "AFTER_FRAMEWORK_INIT";
+ ...
+}
+
The IMMEDIATE
field can have the following values:
"AFTER_FRAMEWORK_INIT"
– The Embedded Activator is called after the Framework is initialized but before the framework is started. This means that no bundles are started yet."BEFORE_BUNDLES_START"
– The Embedded Activator is called after the framework has been started but before the bundles are explicitly started by the launcher. This will always happening in start level 1. If the framework was started from an existing configuration then any bundles in start level 1 that were persistently started will therefore have been started before the Embedded Activator is started. The launcher starts bundles persistently so if the same configuration is restarted they will be started after the framework is started."AFTER_BUNDLES_START"
– Will start the Embedded Activators after all bundles have been persistently started. Since this happens at start level 1, some bundles in higher start levels will not be active.The reason strings are used is to not require the need for extraneous types on the executable’s class path. If a string in IMMEDIATE
is used that is not part of the previous one then a message must be logged. The behavior will then be "AFTER_BUNDLES_START"
. Other strings are reserved for future extensions.
For example:
+ +public class MyActivator implements BundleActivator {
+ public static String IMMEDIATE = "BEFORE_BUNDLES_START";
+
+ public void start(BundleContext context) {}
+
+ public void stop(BundleContext context) {}
+}
+
+bnd.bnd:
+ Embedded-Activator: com.example.MyActivator
+
For backward compatibility reason the IMMEDIATE
field the launcher will also recognize a boolean
field.
true
– Corresponds to the String BEFORE_BUNDLES_START
.false
– Will be the "AFTER_BUNDLES_START"
caseIt is recommended to update to one of the strings instead of the boolean and not use this pattern in new setups.
+ +The -runbundles
instruction supports an startlevel
attribute. If one or more of the bundles listed in the -runbundles
instruction
+uses the startlevel
attribute then the launcher will assign a startlevel to each bundle. This is currently supported for the
+normal launcher and not for Launchpad nor the remote launcher.
If a bundle has a startlevel
attribute then this must be an integer > 0, otherwise it is ignored. Bundles that have no
+startlevel
attribute will be assign the maximum assigned startlevel attribute + 1. For example, given the following
+bundles:
-runbundles: \
+ org.apache.felix.configadmin;version='[1.8.8,1.8.9)',\
+ org.apache.felix.http.jetty;version='[3.2.0,3.2.1)';startlevel=10,\
+ org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\
+ ...
+ osgi.enroute.twitter.bootstrap.webresource;version='[3.3.5,3.3.6)';startlevel=12,\
+ osgi.enroute.web.simple.provider;version='[2.1.0,2.1.1)'
+
The org.apache.felix.configadmin
, org.apache.felix.http.servlet-api
, and osgi.enroute.web.simple.provider
do not specify
+a startlevel
attribute and will therefore be assigned to start level 13. This value is picked because max(10,12)= 12 + 1 = 13.
Startlevels are assigned before the framework is started, they are updated on the fly if during a debug session the setup changes.
+ +Normally in OSGi the begining start level is selected with the system property org.osgi.framework.startlevel.beginning
. If
+this -runproperty
is not set then the launcher will set this property before starting the framework to the maximum of
+specified levels + 2. If a start level management agent is present then this property should be set, the launcher will
+then not interfere.
The bundles are started at during start level 1.
+ +For example, a management agent that manages start levels is placed in start level 1 and all other bundles are placed +at start level 100.
+ +-runbundles: \
+ org.apache.felix.configadmin;version='[1.8.8,1.8.9)',\
+ org.apache.felix.http.jetty;version='[3.2.0,3.2.1)';startlevel=100,\
+ org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\
+ org.example.management.agent;startlevel=1
+ osgi.enroute.twitter.bootstrap.webresource;version='[3.3.5,3.3.6)';startlevel=100,\
+ osgi.enroute.web.simple.provider;version='[2.1.0,2.1.1)'
+
The -runproperties
specify a begining of 1:
-runproperties: \
+ org.osgi.framework.startlevel.beginning=1
+
The launcher will install all bundles and assign them their start level. The framework is then started and moves +to level 1. This starts the management agent. This management agent will then move the start level higher to finally +level 100.
+ +Any bndrun file can be packaged by the launcher plugin. This creates an executable JAR that will contain all its dependencies.
+ +$ bnd package xyz.bndrun
+
You can then execute the jar:
+ +$ java -jar xyz.jar
+
You can override all the properties that are embedded in the executable JAR with the -Dlauncher.properties=<file>
option of the Java VM. Instead of consulting its internal properties file, the launcher will read the given file. However, you can also set individual System properties with the -D
option that override a specific launching constant.
launch.storage.dir
– (-runstorage
) Sets the directory for the framework directory. When org.osgi.framework.storage
is also set, then the OSGi property has priority.launch.keep
– Keeps the OSGi Framework storage aroundlaunch.system.packages
– Extra system packageslaunch.system.capabilities
– Extra system capabilitieslaunch.trace
– Trace the launcher’s progresslaunch.timeout
– Abort after timeout
millisecondslaunch.name
– Name of the executable (normally project name)launch.noreferences
– Do not use the reference:
scheme (-runnoreferences
)launch.notificationPort
– A port to send errors toFor example, if you want to run your executable in trace mode:
+ +$ java -Dlaunch.trace=true xyz.jar
+
An executable JAR can be unzipped in a directory. It will then include a Windows and Linuxy shell script to start the executable in expanded form. In the expanded form, the framework will try to use reference:
URLs to install bundles when not on Windows. (Windows and reference URLs do not work well because Windows keeps files locked.)
If no reference URLs should be used, the -runnoreferences=true
instruction can be set.
Framework properties can be set using the -runproperties
instruction. Framework properties do not include the system properties but any property set using -runproperties
can be overridden with a system property. That is, it is impossible to set a property using the -D
on the Java command line unless it was prior given a default in the bndrun file.
For example:
+ +bndrun:
+-runproperties: foo=3
+
+
+$ java -Dfoo=4 -Dbar=1 xyz.jar
+
In this example, the xyz app will see foo=4
but bar
will not be a framework property.
If the framework stops the launcher will exit. It will set a system exit code that reflects the event type from that the framework returned. The shell script that started the launcher can take the system exit code into account to restart the framework in certain cases.
+ +OSGi FrameworkEvent Launcher Constant Value
+STOPPED STOPPED -9
+WAIT_TIMEDOUT TIMEDOUT -3
+ERROR ERROR -2
+WARNING WARNING -1
+STOPPED_BOOTCLASSPATH_MODIFIED STOPPED_UPDATE -4
+STOPPED_UPDATE STOPPED_UPDATE -4
+
The launcher therefore returns the process exit code UPDATE_NEEDED(-4) when it requires an update. This was chosen over doing an in-process update because it is much safer. So the launching script should look something like:
+ +do {
+ bnd run app.bndrun
+} while ($?==-4)
+
The purpose of the aQute Remote project is to provide remote debugging support for bnd projects. It can be used to debug bundles and bndrun files in a remote machine running an OSGi framework with an agent installed on it; it can also install a framework on a remote machine before it uses the agent. The architecture is heavily optimized to run on small remote machines.
+ +This project is the bnd remote launcher. It consists of the following artifacts:
+ +biz.aQute.remote.launcher
– The actual launcher. You can use the biz.aQute.remote.launcher
by placing it on the -runpath
. This will override the default launcher. It contains the agent and the bnd launcher plugin.biz.aQute.remote.agent
– The agent that must run as a bundle on a framework, or alternatively, runs on the framework side of the classpath and uses the framework’s Bundle Context. That is, you can put this file on the -runpath
of the biz.aQute.launcher
bndrun and bnd files.biz.aQute.remote.main
– An executable JAR.The biz.aQute.remote.launcher
should be placed on the -runpath
in a bnd or bndrun file. The remote plugin expects a -runremote
instruction in the parent file. This property has the following syntax:
-runremote ::= remote (',' remote)*
+remote ::= NAME ( ';' aspect '=' value ) *
+aspect ::= 'jdb' | 'shell' | 'host' | 'agent' | `timeout`
+
It is possible to specity multiple remote clauses. All sections are started simultaneously. The aspects are described in the following sections:
+ +jdb
– The JDB debug port. This is the port that will be opened by the debugger in the IDE. If this aspect is not set, a number is assigned from 16043, incremented for every session. The debugger will wait until this port becomes available on the required host.host
– The name of the remote host. Default localhost
. Notice that for some Unixes there is a confusion what localhost is, some Linux variants make localhost 127.0.1.1 while the original is 127.0.0.1. So better make sure.shell
– The shell that should be used. There are a number of possibilities for this value
+ ++1 – A TCP port. The launcher will attach the port from the remote host and forward any I/O.
+
agent
– The port on which the agent is listening, the default is ${aQute.agent.server.port}.timeout
– Timeout in seconds for the debug connectionAdditionally, the communication can be traced by setting the aQute.remote.util.Link.trace
system property to true
on the target system.
An example remote bndrun file:
+ +local = \
+ local; \
+ shell = 4003; \
+ jdb = 1044; \
+ host = localhost
+
+-runremote: ${local}
+-runfw: org.apache.felix.framework;version='[4,5)'
+-runee: JavaSE-1.8
+-runproperties: gosh.args=--noshutdown, osgi.shell.telnet.port=4003
+
+-runpath: biz.aQute.remote.launcher;version=latest
+-runrequires:\
+ osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.shell)',\
+ osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.command)'
+
+-runbundles:\
+ launch.simple.really;version=latest, \
+ org.apache.felix.gogo.shell,\
+ org.apache.felix.gogo.command,\
+ org.apache.felix.gogo.runtime, \
+ org.apache.felix.shell.remote
+
One example where a javaagent is used is the JaCoCo Java Code Coverage Library. +The following example shows how Jacoco v0.8.5 can be started by adding the -javaagent parameter via -runvm as a VM-option.
+ +create new myapp.coverage.bndrun which inherits from parent myapp.bndrun
+ + -include: myapp.bndrun
+
+ -runvm.coverage: -javaagent:"${repo;org.jacoco:org.jacoco.agent:jar:runtime;latest}=output=tcpserver,jmx=true"
+
A few notes about the example:
+ +the additional parameters =output=tcpserver,jmx=true are JaCoCo-specific to start the agent as tcpserver and also expose functionality via JMX
+One of the primary authors of bnd has always objected to startlevels. His motivation was twofold:
+ +With this disclaimer out of the way, there are actually cases where startlevels are quite important to +improve the non-functional aspects.
+ +Run configurations in bnd are described in bndrun files. The list of bundles to run are listed in the -runbundles
+instruction. The runbundles
instruction has a startlevel
attribute that specifies the startlevel of each bundle.
Since the -runbundles
instruction is frequently calculated by the resolver support in bnd it is not possible to manually
+assign the startlevel
attributes to specific bundles. For this reason there is a decorator support in bnd for some selected
+instructions. A decorator is a header with the same name but ending with a plus sign (+
). This instruction acts like a selector
+and can be used to assign startlevel
attributes on the -runbundles
.
For example:
+ +-runbundles: \
+ org.apache.servicemix.bundles.junit;version='[4.13.0,5)',\
+ org.apache.felix.log, \
+ demo;version='[1.0.0,1.0.1)'
+
+-runbundles+: \
+ demo;startlevel=11,\
+ *;startlevel=99
+
The launcher is responsible for installing the bundles after starting or communicating with a framework. The
+default bnd launcher will assign each bundle a startlevel, where bundles that do not have a specified startlevel
are
+assigned one level higher than the maximum specified levels.
The launcher supports narrow managed mode or all via the [-launcher
] instruction. In narrow mode, the start levels
+will only be assigned to the scope, the set of bundles listed in the run bundles. In the all mode, all bundles
+that are installed at launch time will be assigned the default start level.
The default launcher will then move the framework start level to 2 higher than the highest specified start level.
+ +The resolve support in bnd can automatically assign startlevel
attributes to the -runbundles
based on different ordering
+strategies. There are two strategies that use the dependency graph. This graph can be sorted in topological order.
+This means that a resource is always listed ahead of any of its dependencies when there are no cycles.
The -runstartlevel instruction controls the ordering and assigned start levels.
+ +At the time of this writing it is not yet clear what the best strategy is, and it may depend.
+ +Ordering by the topological sort (a resource is followed by its dependencies) will start something like the log service +last and any applications bundles first. Although at first sight this feels wrong (the log service should be started first +to capture the events at startup) it does solve the jojo problem because one of the last bundles started will be the +Service Component Runtime that will activate all components. At that time all initialization should have taken place.
+ +Reversing the topplogical sort will start the something like SCR and log first because they have few or no dependencies. +Application bundles are then started latest.
+ +Traditionally start levels are managed by an application bundle. Shell commands can increase and decrease start levels. +For this reason, start levels often use nice numbers.
+ +Using the resolver the need for nice numbers is diminished since bnd is now taking care. The basic default model +is to assign bundles sequentially in selected order from an initial level stepping with 10. The -runstartlevel instruction +can provide the initial level and the step if desired.
+ +The launcher will by default move the framework then to a start level that includes any used start levels. This
+default behavior can be blocked by specifying the org.osgi.framework.startlevel.beginning
property. If this
+property is set bnd will assume that there is an agent that will handle the runtime start levels.
A quick way to disable the start level handling by the launch is to set the property used to convey the default
+start level, launch.startlevel.default
to 0. This will disable the complete start level handling regardless
+of other settings.
This section describes how bnd implements a pluggable testing framework. With most tools that use bnd this information is hidden behind a pleasant GUI (sometimes). However, in certain cases it is necessary to understand how bnd handles testing.
+ +In the classic way testing consists of launching a new VM, dynamically loading the classes to be tested and then running the test framework on those. As this violates module boundaries as if they did not exist there is another way.
+ +In bnd there is a header that allows a bundle to define what tests it contains:
+ +Test-Cases ::= fqn+
+
Such a bundle therefore exports a number of test cases to the environment. These test cases can then be executed by the tester. The default tester in bnd is the biz.aQute.tester
bundle, but this can be overridden because the launcher and the tester are pluggable (see Other Tester Frameworks).
The default tester can run in one shot mode or in automatic mode. The one shot mode is specified using the tester.names
property - the tester will run the specified tests and then exit. This mode is typically used by build tools, which set the tester.names
property, run the tests, and then parse the results.
In automatic mode, the default tester creates a list of available bundles with the Test-Cases
header set and executes all of them. Automatic mode will then end, or if the tester.continuous
property is set it will continue running (known as continuous mode).
In continuous mode, every time a test bundle is started the tester will run that bundle’s tests. Continuous mode is intended for developing test bundles. You just run a framework and edit your test bundle’s code. Any changes are saved and deployed, triggering a restart of the bundles and hence a re-run of the tests.
+ +Test-Cases
header AutomaticallyThe Test-Cases
header can be set by hand but this can become a maintenance nightmare. A very useful macro is the ${classes}
macro. This macro inspects the JAR and can find all classes that are considered test cases:
Test-Cases = ${classes;CONCRETE;EXTENDS;junit.framework.TestCase}
+
This example looks for concrete classes that extend the JUnit 3 base class. For non JUnit 3 tests, you can use a naming convention:
+ +Test-Cases: ${classes;CONCRETE;PUBLIC;NAMED;*Test*}
+
This will include all concrete public classes whose name includes the word Test
.
biz.aQute.tester
If you do not explicitly specify the tester module to use, bnd will use biz.aQute.tester
. The configuration of this module is as follows.
The default tester uses the project information to parameterize the tester’s runtime component. However, it is also possible to set these runtime parameters explicitly with framework properties when you want to run the framework in automatic mode. The default tester obeys the following framework properties:
+ +Property | +Default | +Description | +
---|---|---|
tester.port |
+ - | +The port to send the JUnit information to in a format defined by Eclipse. If this property is not set, no information is send but tests are still run. | +
tester.host |
+ localhost |
+ The host to send the JUnit information to in a formatted defined by Eclipse. If this property is not set localhost is assumed. | +
tester.names |
+ + | Fully qualified names of classes to test. If this property is null automatic mode is chosen, otherwise these classes are tested and then the test exits. |
+
tester.dir |
+ testdir |
+ A directory to put the test reports. If the directory does not exist, no reports are generated. The default is testdir in the default directory. This directory is not automatically created so to see the results it is necessary to ensure such a directory exists. the files in the test directory are usable in Hudson/Jenkins as test reports |
+
tester.continuous |
+ false |
+ In automatic mode (ie, when no tester.names are set), continue watching the bundles and (re-)run a bundle’s tests when it is started. |
+
tester.trace |
+ false |
+ Trace the test framework in detail. Default is false, must be set to true or false. | +
To setup an environment to test continuously, the following launcher configuration can be used:
+ +Test-Cases: ${classes;CONCRETE;EXTENDS;junit.framework.TestCase}
+-runfw: org.apache.felix.framework
+-buildpath: osgi.core;version='[4.1,5)', \
+ osgi.cmpn, \
+ junit.osgi
+Private-Package: org.example.tests
+-runtrace: true
+-runbundles: biz.aQute.junit
+-runproperties: \
+ tester.trace=true, \
+ tester.continuous=true, \
+ tester.dir=testdir
+
The example setup creates a bundle containing the org.example.tests
package and sets the Test-Cases
header to all JUnit 3 test cases in that package. If you run this setup, it runs the project bundle with the biz.aQute.junit
bundle. This tester bundle is parameterized with the tester.*
properties to have trace on, continuous mode on, and to put the test reports in ./testdir
.
You can find a bndtools project that shows this at Github.
+ +biz.aQute.tester.junit-platform
As of Bnd 5.0, bnd includes a new tester bundle biz.aQute.tester.junit-platform
that supports JUnit 5.
As per the JUnit 5 documentation, JUnit 5 is comprised of three modules:
+ +biz.aQute.tester.junit-platform
leverages JUnit Platform for discovering and launching tests. This tester will:
TestEngine
implementations;Test-Cases
header set, or with any classes matching the -tester.testnames
property;biz.aQute.tester
At the moment, biz.aQute.tester.junit-platform
supports most of the features of biz.aQute.tester
, including continuous testing and XML reporting. There are a couple of notable exceptions:
biz.aQute.tester
uses a number of mechanisms to inject a BundleContext
into a running test case. Due to significant architectural differences between JUnit Platform and JUnit 3/4, this feature is not yet supported by biz.aQute.tester.junit-platform
and may never be fully supported (or at least, not in a way that is 100% backward compatible). However, it is not difficult to work around this by manually fetching the bundle context in your tests using FrameworkUtil
(eg, in a @Before
method).junit-platform-launcher
>= 1.4.0.In addition to JUnit Platform support, the new tester has some features that weren’t available in the old tester, which makes its integration with Eclipse a bit more user-friendly:
+ +biz.aQute.tester
, when running in continuous mode only the results of the first test run are displayed in Eclipse. With biz.aQute.junit-platform
, if you select the Display JUnit results in IDE every time the tester reruns tests property in the launch configuration, then it will display the results afresh for every test run. This allows you to combine the power of continuous testing with the convenience of Eclipse’s JUnit GUI.TestExecutionListener
sJUnit Platform has a publicly-defined TestExecutionListener
interface. As of Bnd 5.2, biz.aQute.tester.junit-platform
has support for adding your own custom TestExecutionListener
implementations: simply write a class that implements this interface, and then register it as a OSGi service before the test run starts. biz.aQute.tester.junit-platform
will invoke the callback methods on your custom listener(s) as the tests are executing.
This feature can be useful if you want to do any custom reporting for your tests - it is likely to be much easier and more flexible than (eg) trying to manipulate XML files generated by the legacy XML reporting module.
+ +biz.aQute.tester.junit-platform
-tester: biz.aQute.tester.junit-platform
in your bnd file (see Other Tester Frameworks).biz.aQute.tester.junit-platform
’s dependencies are installed in your -runbundles
.-runbundles
.Bnd can help with the last two steps by adding the tester and engine bundles to -runrequires
and using the resolver:
-runrequires: \
+ bnd.identity;id='org.junit.jupiter.engine',\
+ bnd.identity;id='org.junit.vintage.engine',\
+ bnd.identity;id='biz.aQute.tester.junit-platform'
+
See the chapter on resolving for more information.
+ +Note that if you’re only using JUnit 3/4, you can omit the -runrequires
line for the Jupiter engine, and conversely if you’re only using JUnit Jupiter you can omit the Vintage engine. Alternatively/additionally, if you have any other TestEngine
implementation bundles available, you can list these here instead/as well (though this has not been tested).
As noted above, biz.aQute.tester.junit-platform
requires JUnit Platform (and its dependencies) on the classpath, and if it is to do much that is useful it will also require at least one TestEngine
. Bundled versions of these are part of Eclipse since Oxygen. You can include them in your workspace from:
-plugin.repository: \
+ aQute.bnd.repository.osgi.OSGiRepository;\
+ name="Eclipse 2018-12";\
+ locations="https://bndtools.jfrog.io/bndtools/bnd-build/eclipse/4.10/index.xml.gz";\
+ poll.time=-1;\
+ cache="${workspace}/cnf/cache/stable/Eclipse-2018-12"
+
-plugin.repository: \
+ aQute.bnd.repository.p2.provider.P2Repository;\
+ name="Eclipse Local";\
+ url="file:///path/to/eclipse/";\
+ location="${workspace}/cnf/cache/stable/EclipseLocal"
+
Alternatively, it is not difficult to download the required (non-OSGi) modules from Maven Central and include them as-is on -runpath
, or else (preferably) wrap them into bundles and include them in -runrequires
/-runbundles
.
+As of JUnit 5.6, the JUnit jars already have the OSGi metadata and so can be used as bundles
+direct from Maven Central.
Also note that unfortunately, due to a bug in biz.aQute.tester.junit-platform
, Bndtools 5.0
+does not work with JUnit 5.5+. A fix is already available in the latest development snapshot, and we expect the
+fix to be included in a future Bndtools release (hopefully soon).
The biz.aQute.tester is a normal bundle that gets started from the launcher framework. However, before bnd chooses the default tester, it scans the classpath for a tester (set with -runpath
) for JARs that have the following header set:
Tester-Plugin ::= fqn
+
If no such tester is found on the -runpath
it will look in the -tester
instruction and loads that bundle:
-tester: biz.aQute.junit
+
Otherwise it will use biz.aQute.tester
(if it still can find it).
The Tester-Plugin
header points to a class that must extend the aQute.bnd.build.ProjectTester
class. This class is loaded in the bnd environment and not in the target environment. This ProjectTester plugin then gets a chance to configure the launcher as it sees fit. It can get properties from the project and set these in the Project Launcher so they can be picked up in the target environment.
As this is a quite specific area the rest of the documentation is found in the source code.
+ +For a long time bnd had biz.aQute.junit
as the default tester. biz.aQute.junit
has the same functionality as biz.aQute.tester
, but with the following key differences:
biz.aQute.junit
embedded the JUnit 3/4 classes and exported them. biz.aQute.tester
imports the JUnit classes like any other bundle, giving you flexibility in which version you wish to use.biz.aQute.junit
added itself to the -runpath
and then executed the tests from there, making itself (and JUnit) part of the system bundle. In contrast, biz.aQute.tester
adds itself to -runbundles
.Unfortunately the design of biz.aQute.junit
caused constraints between JUnit and bnd that was not good because JUnit itself is not directly a shining example of software engineering. :-( So for this reason, biz.aQute.tester
(or the newer biz.aQute.tester.junit-platform
) is generally preferred.
If for some reason you need to be backward compatible with the older model, set:
+ +-tester: biz.aQute.junit
+
note This featue is in beta. Feedback welcome and expect a few deficiencies in documentation and usage.
+ +An OSGi framework poses special challenges to testing because it is necessary to start a framework instance for each +test. There has always been a bnd OSGi testing framework that was developed to test the OSGi specifications and reference +implementations. This testing framework packaged the tests as a bundle and had a special version of JUnit that could run +these tests from inside the framework. Actually quite powerful and it was fully integrated with Eclipse JUnit testing, +delivering identical output to the CI build tools. However, the use of a special JUnit runner excluded it for people that wanted +to use TestNG or other test frameworks.
+ +A second problem was that since tests ran inside the framework as a bundle they could not influence the setup of the OSGi +framework easily. A last problem was that tests shared the same framework which could result in ordering dependencies.
+ +Launchpad is a bnd runtime library that provides an API to launch a framework that is fully integrated with the +bnd workspace. It automatically exports the runtime class path via the framework bundle, ensuring there is a single class space for +the code on the class path and the code in the bundles. (This does require that bundles properly import their +exported packages.)
+ +Launchpad provides a builder that incrementally can build up the specifications of the framework. The builder can
+take bndrun
files or bundle specifications in the same format as that are used to set the -buildpath
or
+-runbundles
in the bnd.bnd
files. Once the information is setup, bnd will calculate the setup based on
+the classes in the test
folder and launch an OSGi framework.
Once the framework is launched, Launchpad can then inject services and some key framework objects into annotated +fields. Each field can specify a timeout, target filter, and minimum cardinality. Injection can take place in any object but is +usually on the test instance.
+ +In the original OSGi testing support test bundles had to be created during the build. In the OSGi test cases for the Blueprint
+reference implementation more than 200 test bundles were used. Although the overhead was relatively small due to the bnd -make
facility,
+it was still a nuisance because the information in a test case had to be synchronized in a bnd
file in another directory. For
+this reason Launchpad contains a bundle builder. This bundle builder used bnd
under the hood. It can use anything that a bnd
+sub bundle could use; it is build in the context of the project that contains the test classes and shares the same
+-buildpath
. Bundles that are build with the bundle builder can actually leverage nested classes in the test class for
+Bundle Activator or component classes.
Overall this is a comprehensive library for testing OSGi projects in a Bndtools workspace.
+ +You need the biz.aQute.launchpad
library on your -testpath
.
bnd.bnd:
+...
+-buildpath: ...
+-testpath: \
+ osgi.enroute.junit.wrapper, \
+ biz.aQute.launchpad
+
note The biz.aQute.launchpad is available in 4.2 but there are a few minor API changes that did not make it. You can +therefore use the snapshot version on JFrog or download the latest version and use:
+ +lib/biz.aQute.launchpad.jar;version=file
+
The next step is to enable your workspace for launchpad. A bnd workspace can have a Remote Workspace Server and
+Launchpad needs it. You therefore need to add the following to your cnf/build.bnd
file.
cnf/build.bnd:
+ -remoteworkspace true
+
Using JUnit, we can now create a test. We start with creating a LaunchpadBuilder
. This builder stores information about the to be started framework. It has many
+methods that usually align with the properties for a bnd.bnd
file. The Javadoc contains the details. We could create this
+object in a @Before
method and close it in an @After
method but this object does not have to be closed. It contains only
+the settings.
LaunchpadBuilder builder = new LaunchpadBuilder()
+ .runfw("org.apache.felix.framework");
+
For this first quick start test we inject a Bundle Context, the core OSGi object that allows us to interact with the framework.
+ +@Service
+BundleContext context;
+
The method is a normal JUnit test method. We open the Launchpad in a try resource block. Closing the Launchpad
object will
+shutdown the framework. The test is simply verifying that the injection has worked.
@Test
+public void quickStart() throws Exception {
+ try (Launchpad launchpad = builder.create()
+ .inject(this)) {
+ assertNotNull(context);
+ }
+}
+
Voila! The first Launchpad test case.
+ +The Launchpad code can be used in different modes:
+ +To use the Launchpad Runner it is necessary to add an @RunWith
annotation on your JUnit test class:
@RunWith(LaunchpadRunner.class)
+public class TestMyCode {
+
+ LaunchpadBuilder builder = new LaunchpadBuilder().runfw("org.apache.felix.framework").debug();
+
+ @Service
+ Launchpad launchpad;
+
+ @Test
+ public void testMyCode() {
+ launchpad.report();
+ }
+}
+
The Launchpad Runner is in control to gather the tests and then execute them. The test gathering is handled via the +standard JUnit support. However, when the test must run, LaunchpadRunner creates a bundle that has the following qualities:
+ +The runner then launches a framework based on a LaunchpadBuilder that it finds in the the static
field builder
.
It then installs the bundles, and adds the test bundle. To execute a test, it loads the +class from the test bundle and finds the appropriate method, instantiates the class in an instance, runs the injector +on this object. and executes the methods.
+ +This mode is similar to the PAX Exam model. It has similar constraints. It does run the @Before
and @After
annotated
+methods but it cannot run the @BeforeClass
and @AfterClass
.
Launchpad is quite awesome to use but there are some pitfalls to take into account. It is strongly recommended +to read this section to get an idea how Launchpad handles class sharing between the test classes (which are on the +normal Java class path) and the classes in bundles. There is more going on than what one suspects looking at the +simplicity how it can be used. This section details the workings to make you aware of potential bugs and should +help in debugging problems. In general, the cleaner your code base, the better this all works. If you have a very messy +setup with lots of scripts, fragments, require bundle, and very wide code interfaces instead of services then this +might not be for you …
+ +In Java, class are loaded from the classpath. The class path is a (usually very long) list of Jar files. When a class +needs to be loaded, Java searches al those Jars for that class, first one wins.
+ +In OSGi, this model is changed for a delegating model. Each bundle imports a set of packages and exports a set +of packages. This information is in a bundle’s manifest. When an OSGi framework resolves a bundle, it wires these +imports to a corresponding export.
+ +When a test case gets started the driver (Eclipse, Gradle, etc.) launches a new Java VM. The class path for that
+VM will consist of all entries on the -buildpath
, -testpath
, and the main and test output folders. When the
+Launchpad builder is first called it will contact the Remote Workspace in the driver and request for an analysis of
+the test code. The Remote Workspace then uses the project setup to calculate a bundle that would export all the
+test code and its imports. The Launchpad Builder then makes the OSGi framework export all packages that that
+virtual test bundle would have exported. That is, any class visible from the test cases will be exported by
+the OSGi framework by default. It is possible to exclude exports using glob expressions or predicates from a test case.
+See the excludeExport()
methods.
A bundle installed on the OSGi Framework should this see all the relevant classes from the class path instead of +from other bundles. The tricky case is when a bundle exports a package that is also available from the +class path. If this package is only exported then the framework cannot substitute it for the package from the +class path. In such a bad case the bundle that exports it will see its embedded version of the class while +the rest of the system sees the version from the class path. This can then result in a class cast exception +like:
+ +java.lang.ClassCastException: an instance of org.example.Foo cannot be assigned to org.example.Foo
+
Although the names of those classes are identical, the problem is that they will be loaded by different class +loaders.
+ +Due to this setup there is very strict sensitivity to the version of the OSGi Framework packages and bundles. Most bnd projects
+have the OSGi framework packages on the -buildpath
. The version of these packages can be lower than the version
+of the Framework because of backward compatibility. Actually, bnd generally recommends to compile against the
+lowest possible framework packages. However, with launchpad these packages will also be used by the Framework, there
+is unfortunately no good way around this. The consequence is that the -buildpath
version of the Framework
+packages must match the exact version used by the implementation of the framework.
Launchpad will calculate the set of packages that are exported by the framework from the claspath. The so called
+org.osgi.framework.system.packages.extra
. It calculates this by creating bundle from the test sources, adding
+all dependencies, and then exporting the full content. That export statement is then uses for org.osgi.framework.system.packages.extra
.
However, this is generally too wide since it includes all dependencies, not just public dependencies. Version mismatches +can create nasty problems and sometimes the solution is to exclude exports.
+ +The Launchpad Builder provides a number of methods called excludeExport()
that take either a glob or a predicate.
+The globs/predicates are then ran against the list of calculated export package names. Any matching entry is then
+not exported.
Launchpad b = new LaunchpadBuilder()
+ .excludeExports( "slf4j.*")
+ .create();
+
If a bndrun file is the -excludeexports
instruction can be placed in the bndrun file containing a list globs.
-excludeexports aQute.lib*, slf4j.*
+
The Launchpad has a default name of the method and class that called create()
. These names can be overwritten with
+create(name)
and create(name,className)
. The actual name of Launchpad is set under the following framework
+property names:
launchpad.name
+launchpad.className
+
Clearly the best part of Launchpad is that you can actually use real services and do not have +to mock them up. Many a test seems to mostly test their mocks.
+ +With Launchpad, real framework is running. You can inject services or register services.
+ +In the following example we register a service Foo
and then verify if we can get it. We then
+unregister the service and see that it no longer exists.
interface Foo {}
+
+@Test
+public void services() throws Exception {
+ try (Launchpad launchpad = builder.create()) {
+
+ ServiceRegistration<Foo> register =
+ launchpad.register(Foo.class, new Foo() {});
+ Optional<Foo> s =
+ launchpad.waitForService(Foo.class, 100);
+ assertThat(s.isPresent()).isTrue();
+
+ register.unregister();
+
+ s = launchpad.waitForService(Foo.class, 100);
+ assertThat(s.isPresent()).isFalse();
+ }
+}
+
The waitForService
methods take a timeout in milliseconds. Their purpose is to provide some leeway
+during startup for the system to settle. If a service should be there then it the getService()
methods
+can be used.
Injection is not automatic because in many cases you want to handle the setup of the framework before +you inject. Injection can also happen as often as you want. However, you first need to create and +start the framework.
+ +@Test
+public void inject() throws Exception {
+ try (Launchpad launchpad = builder.create()) {
+ ServiceRegistration<Foo> register = launchpad.register(Foo.class, new Foo() {});
+
+ class I {
+ @Service
+ Foo foo;
+ @Service
+ Bundle bundles[];
+ @Service
+ BundleContext context;
+ }
+ I inject = new I();
+ launchpad.inject(inject);
+ assertThat(inject.bundles).isNotEmpty();
+ }
+}
+
Although Bundle-Activator’s are not recommended to be used (they are singletons), they are very useful in +test cases. With Launchpad it is not necessary to to make a separate bundle, we can make a bundle with +an inner class as activator.
+ +We therefore first define the Bundle Activator as a static public inner class of the test class:
+ +public static class Activator implements BundleActivator {
+ @Override
+ public void start(BundleContext context) throws Exception {
+ System.out.println("Hello World");
+ }
+ @Override
+ public void stop(BundleContext context) throws Exception {
+ System.out.println("Goodbye World");
+ }
+}
+
The Launchpad class contains a special Bundle builder. This bundle builder is based on bnd and can can do everything that
+bnd can do in a bnd.bnd
file. In this case we add the Bundle Activator and start it.
@Test
+public void activator() throws Exception {
+ try (Launchpad launchpad = builder.create()) {
+
+ Bundle start = launchpad.bundle()
+ .bundleActivator(Activator.class)
+ .start();
+ }
+}
+
When the test is run the output is:
+ +Hello World
+Goodbye World
+
Since the bundle builder can do anything bnd can do, we can also use inner classes for components. These inner
+classes must be static and public (this is an OSGi DS requirement). The following is an example of a class
+that depends on the Bar
service.
interface Bar {
+ void bar();
+}
+
+@Component
+public static class C {
+ @Reference Bar bar;
+ @Activate void activate() { bar.bar(); }
+}
+
+@Test
+public void component() throws Exception {
+ try (Launchpad launchpad = builder
+ .bundles("org.apache.felix.log")
+ .bundles("org.apache.felix.scr")
+ .bundles("org.apache.felix.configadmin")
+ .create()) {
+
+ Bundle b = launchpad.component(C.class);
+ AtomicBoolean called = new AtomicBoolean(false);
+ launchpad.register(Bar.class, ()-> called.set(true) );
+ assertThat(called.get()).isTrue();
+ }
+}
+
Adding a component will return a Bundle. Uninstalling the bundle will remove the component.
+ +Clearly there are lots of things that can go wrong. You can therefore activate the debug()
on the builder or
+Launchpad. This will provide logging to the console.
@Test
+public void debug() {
+ try (Launchpad launchpad = builder
+ .debug()
+ .create()) {
+
+ }
+}
+
If you run this the console will show a lot of diagnostics information.
+ +One of the great innovations in bndtools is the resolver. So far we’ve assembled the list of bundles to run ourselves.
+However, we can also use a bndrun
file and bndrun
files can be resolved.
@Test
+public void bndrun() {
+ try (Launchpad launchpad = builder
+ .bndrun("showit.bndrun")
+ .create()) {
+ }
+}
+
If the bndrun
file contains the following after resolving:
-runrequires: \
+ osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.shell)',\
+ osgi.identity;filter:='(osgi.identity=biz.aQute.bnd.runtime.gogo)'
+
+
+-runbundles: \
+ org.apache.felix.gogo.runtime;version='[1.1.0,1.1.1)',\
+ org.apache.felix.gogo.shell;version='[1.1.0,1.1.1)',\
+ biz.aQute.bnd.runtime.gogo;version=snapshot,\
+ org.apache.felix.log;version='[1.0.1,1.0.2)'
+-runfw: org.apache.felix.framework;version='[5.6.10,5.6.10]'
+-runee: JavaSE-1.8
+
Then we see the following output:
+ +Welcome to Apache Felix Gogo
+g!
+
The Launchpad object contains a special bundle builder. It provides the same capabilities that bnd already has when
+it creates bundles. You can use all the facilities that you can use in the bnd.bnd
file. Export-Package, Import-Package,
+-includeresource, etc.
The following example shows how to create a bundle with a a special header.
+ +@Test
+public void bundles() throws Exception {
+ try (Launchpad launchpad = builder.create()) {
+ Bundle b = launchpad.bundle()
+ .header("FooBar", "1")
+ .install();
+ String string = b.getHeaders()
+ .get("FooBar");
+ assertThat(string).isEqualTo("1");
+ }
+}
+
When you create a bundle from the Launchpad class then by default you inherit nothing from the enironment. However,
+it is possible to set the parent of the builder. The parent(String)
or parent(File)
method can set multiple
+parents.
parent ::= FILE* ( WORKSPACE | PROJECT )?
+
WORKSPACE
– Inherit from the workspace, excludes inheriting from the project and must be specified last in the set of parents.PROJECT
– Inherit from the workspace, excludes inheriting from the project and must be specified last in the set of parents.FILE
– A file path with forward slashes on all platforms. This must point to a bnd/properties file.The order of the parents is important. Earlier bnd files override the same named value in later bnd files.
+ +Example:
+ +Bundle b = lp.bundle().parent('foo.bnd').parent(WORKSPACE).start();
+
In some scenarios you’d like to hide a service so you can override it with a mocked version. Hiding in OSGi +can be achieved with the [Servic Hooks] services. The easiest way is to hide a service via the Launchpad Builder.
+ + LaunchpadBuilder builder = new LaunchpadBuilder().runfw("org.apache.felix.framework").hide(SomeService.class);
+
If you now create a framework, all services not registered via Launchpad will be invisible to all bundles. That is, +only services registered through Launchpad can be seen by the other bundles in the OSGi framework.
+ +@Test
+public void testHidingViaBuilder() throws Exception {
+ try (Launchpad fw = builder.runfw("org.apache.felix.framework")
+ .create()) {
+
+ boolean isHidden = fw.getServices(String.class)
+ .isEmpty();
+ assertThat(isHidden).isTrue();
+
+ fw.framework.getBundleContext()
+ .registerService(String.class, "fw", null);
+
+ isHidden = fw.getServices(String.class)
+ .isEmpty();
+ assertThat(isHidden).isTrue();
+
+ ServiceRegistration<String> visibleToAllViaTestbundle = fw.register(String.class, "Hello");
+
+ assertThat(fw.getServices(String.class)).containsOnly("Hello");
+ visibleToAllViaTestbundle.unregister();
+
+ isHidden = fw.getServices(String.class)
+ .isEmpty();
+ assertThat(isHidden).isTrue();
+ }
+}
+
Although hiding via the Launchpad Builder is the easiest way to hide services, it has the disadvantage that all
+tests hide the same service(s). It is also possible to handle the service hiding in a more controlled way
+by hiding via the Launchpad object. Using this function does require a bit of orchestration. Once you hide
+a service it becomes invisible to bundles that look for that service later. However, bundles that already
+obtained this service will not lose sight of it. It is therefore necessary to hide a service before the
+corresponding framework is started. Since the default is automatic start, the automatic start must be
+disabled with the nostart()
method on the builder.
After the framework is then created. the service is hidden and then the framework is started.
+ +@Test
+public void testHiding() throws Exception {
+ try (Launchpad fw = builder.runfw("org.apache.felix.framework")
+ .nostart()
+ .create()) {
+
+ @SuppressWarnings("resource")
+ Closeable hide = fw.hide(String.class);
+ fw.start();
+
+ boolean isHidden = fw.getServices(String.class)
+ .isEmpty();
+ assertThat(isHidden).isTrue();
+
+ fw.framework.getBundleContext()
+ .registerService(String.class, "fw", null);
+
+ isHidden = fw.getServices(String.class)
+ .isEmpty();
+ assertThat(isHidden).isTrue();
+
+ ServiceRegistration<String> visibleToAllViaTestbundle = fw.register(String.class, "Hello");
+
+ assertThat(fw.getServices(String.class)).containsOnly("Hello");
+ visibleToAllViaTestbundle.unregister();
+
+ isHidden = fw.getServices(String.class)
+ .isEmpty();
+ assertThat(isHidden).isTrue();
+
+ hide.close();
+ assertThat(fw.getServices(String.class)).containsOnly("fw");
+ }
+}
+
As you can see from the test code, the hide
method in this case returns a Closeable
. This object can be used to
+remove the hiding of the given service.
To diagnose any issues it is important to realize that there are some special rules around which bundle does what. Launchpad +has the Bundle Context of the OSGi Framework as well as a special test bundle. The test bundle is empty but it used to have +a Bundle Context for the test code that runs outside the Framework. When you hide a service it will register +Service Hooks that only let services from the test bundle pass through, all other services of the given type are removed +from visibility.
+ +Closeable Launchpad.hide(SomeService.class);
+
The service will remain hidden until you close the Closeable
.
Java developers face a challenge today of building JPMS libraries, let alone when adding a secondary goal that those libraries be usable OSGi bundles. Accuracy and consistency of metadata can often become a problem reducing the time spent focusing on more productive aspect of the library.
+ +A key OSGi innovation is the use of annotations to significantly reduce (in many cases to completely eliminate) the configuration needed to describe an OSGi bundle in build descriptors. See Bundle Annotations.
+ +Just one of the additional benefits of using bnd is the ability to generate the module-info.class
along side all of the traditional OSGi metadata.
When applying the instruction -jpms-module-info
bnd will collect any relevant information and using several heuristics automatically generates the module-info.class
.
When calculating the module name the following is considered:
+ +-jpms-module-info
instruction contains a key, the key is used as the module name
+e.g. -jpms-module-info: foo.module
, the module name is foo.module
Automatic-Module-Name
header is found in the manifest, the value is used as the module nameBundle-SymbolicName
(as calculated) is used as the module nameWhen calculating the module version, the following is considered:
+ +-jpms-module-info
instruction contains a key having a version
attribute, the version
attribute value is used as the module version
+e.g. -jpms-module-info: foo.module;version=1.2.3
, the module version is 1.2.3
Bundle-Version
(as calculated) is used as the module versionWhen calculating the module access, the following is considered:
+ +-jpms-module-info
instruction contains a key having an access
attribute, the access
attribute value is used as the module access. Multiple comma separate values are allowed but quotes are required.
+e.g. the module access is 0x0020
(OPEN):
+ -jpms-module-info: foo.module;access=0x0020
+
With multiple flags:
+-jpms-module-info: foo.module;access='OPEN,SYNTHETIC'
+
Legal values are:
+OPEN
or open
, 0x0020
, 32
SYNTHETIC
or synthetic
, 0x1000
, 4096
MANDATED
or mandated
, 0x8000
, 32768
Require-Capability
contains any capability in the osgi.extender
namespace, the module access is open0
Note that for the above rules, the earliest matching rule wins.
+ +When calculating the module requires the following is considered:
+ +If the -jpms-module-info
instruction contains a key having a modules
attribute, the modules
attribute value is first split on commas (,
) and each segment is added as a raw required module name
+e.g. -jpms-module-info: foo.module;modules='java.desktop,java.logging'
, the modules java.desktop
, and java.logging
are added to module requires
In addition, if the -jpms-module-info
instruction contains a key having a ee
attribute, the ee
attribute indicates the Java module name mapping table to use for Java SE packages using bnd’s aQute.bnd.build.model.EE
definitions which define a set of Java module name mapping tables keyed by EE
.
+e.g. -jpms-module-info: foo.module;ee=JavaSE-10
, bnd will use the Java module name mapping table for Java SE 10 when determining module name for a given Java SE package
If no ee
attribute is specified, bnd will use the Java module name mapping table for Java SE 11 when determining module name for a given Java SE package
If an imported package is associated with a module name, the module is added to module requires
+ +Note: Non-Java SE packages are associated with module names by indexing all packages on the classpath of the bnd analyzer
where the providing jar’s module’s name is:
module-info.class
Automatic-Module-Name
-\d
)Bnd will set the access to transitive
if any package exported by the bundle has a uses
constraint on a package of the required module.
Bnd will set the access to static
if the module is specified in the -jpms-module-info
instruction and does not actual have any imports.
Bnd will set the access to static
if all the packages imported from the module are any combination of resolution:=optional
, resolution:=dynamic
or match the Dynamic-ImportPackage
instruction.
Bnd does not currently track a require
’s version.
Module exports will be mapped from all OSGi exported packages by default which can be managed easily with use of the Bundle Annotation @org.osgi.annotation.bundle.Export
on package-info.java
.
@org.osgi.annotation.bundle.Export
+package com.acme.foo;
+
Targeted exports (via the exports .. to ..
clause) are supported with use of the @aQute.bnd.annotation.jpms.ExportTo
. This annotation specifies the module name(s) to which a exported is targeted.
@org.osgi.annotation.bundle.Export
+@aQute.bnd.annotation.jpms.ExportTo("com.acme.bar")
+package com.acme.foo;
+
Note: The @ExportTo
annotation is only relevant in conjunction with the @Export
annotation.
Module opens are supported with use of the @aQute.bnd.annotation.jpms.Open
annotation on package-info.java
. This annotation optionally specifies the module name(s) to which the opens is targeted.
@aQute.bnd.annotation.jpms.Open
+package com.acme.foo;
+
Module uses are supported transparently with use of the bnd @aQute.bnd.annotation.spi.ServiceConsumer
SPI Annotation.
Module provides are supported transparently with use of the bnd @aQute.bnd.annotation.spi.ServiceProvider
SPI Annotation.
The module main class attribute is supported with use of the @aQute.bnd.annotation.jpms.MainClass
annotation applied to the main class’s Java type.
@aQute.bnd.annotation.jpms.MainClass
+public class Main {
+ public static void main(String... args) {...}
+}
+
Bnd’s module-info.class
generation is supported when building with Java 8 or higher. (Yes! I did say Java 8.)
There are scenarios where the heuristics used by bnd don’t give the desired result because the necessary information is not available or is incorrect.
+ +The -jpms-module-info-options
instruction provides some capabilities to help the developer handle these scenarios. The instruction uses the package header syntax similar to many other bnd instructions. The keys of these instructions are module names and there are 4 available attributes. They are:
substitute
- If bnd generates a module name matching the value of this attribute it should be substituted with the key of the instruction.
+e.g.
-jpms-module-info-options: java.enterprise;substitute="geronimo-jcdi_2.0_spec"
+
means that if bnd calculates the module name to be geronimo-jcdi_2.0_spec
it should replace it with java.enterprise
ignore
- If the attribute ignore="true"
is found the require matching the key of the instruction will not be added.
+e.g.
-jpms-module-info-options: java.enterprise;ignore="true"
+
means ignore the module java.enterprise
static
- If the attribute static="true|false"
is found the access of the module matching the key of the instruction will be set to match.
+e.g.
-jpms-module-info-options: java.enterprise;static="true"
+
means make the require
for module java.enterprise
static
transitive
- If the attribute transitive="true|false"
is found the access of the module matching the key of the instruction will be set to match.
+e.g.
-jpms-module-info-options: java.enterprise;transitive="true"
+
means make the require
for module java.enterprise
transitive
The following is an example with multiple attributes and instructions:
+ +-jpms-module-info-options: \
+ java.enterprise;substitute="geronimo-jcdi_2.0_spec";static=true;transitive=true,\
+ java.management;ignore=true;
+
Bnd’s export
functionality allows for the creation of executable jars which contain fully assembled applications whose only external dependency is a JDK.
When the -jpms-module-info
instruction is used in a bndrun
file that is the target of the export function the executable jar that is created will contain a generated module-info.class
containing whatever information bnd’s JPMS heuristics could glean from the assembly.
e.g.
+ +-jpms-module-info: \
+ ${project.artifactId};\
+ version=${project.version};\
+ ee=JavaSE-${java.specification.version}
+
The example above produces a module-info.class whose module id comes from the property project.artifactId
, version from the property project.version
and ee
from the property java.specification.version
.
When the -jpms-module-info
instruction is used in a bndrun
file that is the target of the export function bnd launcher’s main class will be applied to the module info implicitly.
OSGi developers face a challenge when using third-party libraries that +are not supplied as OSGi bundles. Though an increasing number of +libraries are available from their original sources as OSGi bundles, and +a large number are available as wrapped OSGi bundles from external +repositories, it is still sometimes necessary to build such a wrapper +ourselves. This technical note details an approach to OSGi bundle +production using only command line tools.
+ +This article details a simple and repeatable process to wrap +arbitrary Java libraries as OSGi bundles, using bnd as a command line +tool.
+ +As a running example, the JDOM library version 1.1.2 will be wrapped as +an OSGi bundle.
+ +NB: Many of the tasks described here can be more easily performed with a +full-featured OSGi IDE such as Bndtools. However, this document is +intended for users who perform these tasks infrequently and do not wish +to download an IDE; instead a single, lightweight +command-line tool is used.
+ +Bnd generates the Import-Package
statement of the output bundle via an
+extremely thorough inspection of the compiled Java. Every bytecode
+instruction of every class file inside the bundle is processed to
+discover dependencies on external packages. Usually the result of this
+inspection is more accurate than we would be able to achieve by manually
+providing the Import-Package
statement.
Unfortunately when wrapping third-party libraries it is sometimes not
+sufficient to simply accept the generated Import-Package
statement:
+the result may need to be fine-tuned. This is because many third-party
+libraries contain dependencies that are out of place, often due to
+errors resulting from a lack of good modular practices.
For example:
+ +Bnd detects dependencies statically by inspecting all code in the +library; it cannot determine which parts of the library are reachable. +For example a common error is to include JUnit test cases in a library +JAR, resulting in dependencies on JUnit. Unless fixed, the bundle will +only be usable in a runtime environment where JUnit is also present, +i.e., we will have to ship a copy of JUnit to our end users.
+ +The problem of checking for and correcting such problems represents the +bulk of the manual effort required in what is otherwise a fairly +automatic process.
+ +We assume that the JDOM library has been downloaded, and jdom.jar
is
+available in the current directory.
In order to wrap as a bundle using bnd, we need an initial “recipe”.
+Create a file named jdom.bnd
containing the following:
-classpath: jdom.jar
+Bundle-SymbolicName: org.jdom
+ver: 1.1.2
+-output: ${bsn}-${ver}.jar
+Bundle-Version: ${ver}
+Export-Package: *;version=${ver}
+
This is a bnd descriptor, and it instructs bnd how to generate the OSGi +bundle. To summarize the features used:
+ +To generate the bundle: bnd reports the name of the generated file +(org.jdom-1.1.2.jar), the number of files contained (79) and its size in +bytes (151K). We refer to this bundle as the initial wrapping.
+ +The intial wrapping may contain dependency errors as described in the
+introduction. Therefore we must examine the Import-Package
statement
+as generated by bnd. Unfortunately, direct viewing of the MANIFEST.MF
+can be difficult due to the unusual formatting and line-wrapping rules
+of the manifest file format that make it quite inaccessible. For
+example:
$ bnd jdom.bnd
+org.jdom-1.1.2.jar 79 154490
+Import-Package: javax.xml.parsers,javax.xml.transform,javax.xml.transf
+ orm.sax,javax.xml.transform.stream,oracle.xml.parser,oracle.xml.parse
+ r.v2,org.apache.xerces.dom,org.apache.xerces.parsers,org.jaxen,org.ja
+ xen.jdom,org.jdom;version="[1.1,2)",org.jdom.adapters;version="[1.1,2
+ )",org.jdom.filter;version="[1.1,2)",org.jdom.input;version="[1.1,2)"
+ ,org.jdom.output;version="[1.1,2)",org.jdom.transform;version="[1.1,2
+ )",org.jdom.xpath;version="[1.1,2)",org.w3c.dom,org.xml.sax,org.xml.s
+ ax.ext,org.xml.sax.helpers
+
Since this is so unreadable, Bnd offers a print command that formats in
+the manifest of a specified bundle JAR. We can request Bnd to print only
+the imports and exports by using the -impexp
switch:
$ bnd print -impexp org.jdom-1.1.2.jar
+[IMPEXP]
+Import-Package
+ javax.xml.parsers
+ javax.xml.transform
+ javax.xml.transform.sax
+ javax.xml.transform.stream
+ oracle.xml.parser
+ oracle.xml.parser.v2
+ org.apache.xerces.dom
+ org.apache.xerces.parsers
+ org.jaxen
+ org.jaxen.jdom
+ org.jdom {version=[1.1,2)}
+ org.jdom.input {version=[1.1,2)}
+ org.w3c.dom
+ org.xml.sax
+ org.xml.sax.ext
+ org.xml.sax.helpers
+Export-Package
+ org.jdom {version=1.1.2, imported-as=[1.1,2)}
+ org.jdom.adapters {version=1.1.2}
+ org.jdom.filter {version=1.1.2}
+ org.jdom.input {version=1.1.2, imported-as=[1.1,2)}
+ org.jdom.output {version=1.1.2}
+ org.jdom.transform {version=1.1.2}
+ org.jdom.xpath {version=1.1.2}
+
Reviewing the imports, we see that most of them come +from JRE packages. However there are three groups of dependencies that +may cause problems: the Oracle XML parser; the Xerces XML parser; and +the Jaxen XPath library. Unless something is done to fix these, our JDOM +bundle will not work unless all three dependencies are present at +runtime.
+ +First we consider the Oracle XML parser dependency. We anticipate that
+this probably derives from an optional feature, and can therefore be
+considered an optional dependency. Importing a package with the
+resolution:=optional
directive allows our bundle to see the specified
+package at runtime if an exported for it is available, but it does not
+prevent the bundle from resolving in the event that an exported is not
+available.
To mark the two Oracle imports as optional, add the following line to
+the jdom.bnd
file:
Import-Package: \
+ oracle.xml.*;resolution:=optional, \
+ *
+
If we regenerate the bundle and again ask Bnd to print the imports and +exports, we will see that the Oracle dependencies are marked as +optional;
+ +[IMPEXP]
+Import-Package
+ ...
+ oracle.xml.parser {resolution:=optional}
+ oracle.xml.parser.v2 {resolution:=optional}
+ ...
+
We can do the same for the Xerces packages:
+ +Import-Package: \
+ oracle.xml.*;resolution:=optional,\
+ org.apache.xerces.*;resolution:=optional,\
+ *
+
NB: the final * in the Import-Package
statements is
+extremely important. Without this, all other dependencies detected
+by Bnd will be omitted from the final manifest.
In some cases the root cause of a dependency is unclear, or we may wish +to obtain further information on the causes of a dependency.
+ +JDOM depends on Jaxen, an XPath evaluation library. However not all
+use-cases for JDOM involve evaluating XPath expressions, so this may be
+an optional dependency. To get further information to help us make this
+decision, we can use the Bnd print
command again with the -usedby
+option:
$ bnd print -usedby org.jdom-1.1.2.jar
+[USEDBY]
+java.sql org.jdom
+javax.xml.parsers org.jdom.adapters
+...
+org.jaxen org.jdom.xpath
+org.jaxen.jdom org.jdom.xpath
+org.jdom
+ .
+ org.jdom.adapters
+ org.jdom.filter
+...
+
This tells us that the Jaxen dependencies (i.e. the +org.jaxen and org.jaxen.jdom packages) are used only from one package in +JDOM, namely org.jdom.xpath. Additionally by looking at the full results +we can see that org.jdom.xpath does not appear on the left hand side, +meaning that it is not imported by any other part of the JDOM library.
+ +If we simply make our Jaxen imports optional, then a client that imports
+org.jdom.xpath
from the JDOM bundle will get NoClassDefFoundError
or
+ClassNotFoundException
when it tries to use the XPath features. In
+this case it is better to separate org.jdom.xpath
into a new bundle.
+Once separated, any client that explicitly needs the XPath features will
+not resolve when the Jaxen bundle offering those features is
+unavailable, which is the desired outcome: it is better to get a
+resolution error than a runtime exception. Separation works in this case
+because the org.jdom.xpath
package has good coherency (i.e., it does
+just one thing) and there are no references to it from elsewhere in
+JDOM. If there were such references then the two bundles would be
+tightly coupled to each other and the separation would be pointless.
In order to separate the bundles, we first need to omit org.jdom.xpath
+from the exports of our main JDOM bundle. This is done by refining the
+Export-Package
statement as follows:
Export-Package: !org.jdom.xpath,\ *;version=${ver}
+
The leading exclamation mark can be read as “not” and it simply excludes +the named package from the generated bundle. Alternative we can just +list each package explicitly, though this requires us to repeat the +version directive on each line:
+ +Export-Package: org.jdom;version=${ver},\
+ org.jdom.adapters;version=${ver},\
+ org.jdom.filter;version=${ver},\
+ org.jdom.input;version=${ver},\
+ org.jdom.output;version=${ver},\
+ org.jdom.transform;version=${ver}
+
We will also need a Bnd descriptor named jdom.xpath.bnd to generate the +JDOM XPath bundle. This is based on our original recipe:
+ +-classpath: jdom.jar
+Bundle-SymbolicName: org.jdom.xpath
+ver: 1.1.2
+-output: ${bsn}-${ver}.jar
+Bundle-Version: ${ver}
+Export-Package: org.jdom.xpath;version=${ver}
+
The previous used-by analysis yielded the package from which an import +dependency resulted. Sometimes we need to dig deeper and find the +individual class(es) responsible for the dependency. Unfortunately this +feature is not available from the Bnd command line, instead we have to +use a macro inside the descriptor file. To find out the class-level +causes for the dependency on the oracle.xml.parser package, add the +following temporary header to the descriptor:
+ +TEMP: ${classes;IMPORTING;oracle.xml.parser}
This will result in a TEMP header being added to the manifest of the +output bundle. To view it, use Bnd’s print command again with the +-manifest option:
+ +$ bnd print -manifest org.jdom-1.1.2.jar
+[MANIFEST org.jdom-1.1.2.jar]
+...
+TEMP org.jdom.adapters.OracleV1DOMAdapter
+...
+
The output shows that the Oracle parser is used only from a single
+class, strongly supporting our opinion that it is an optional
+dependency. Unfortunately we cannot separate it into its own bundle
+because there are several other such adapters in the same package, each
+with their own dependencies. Performing the same analysis on the Oracle
+parser V2 and Xerces dependencies yields similar results: individual
+classes in the org.jdom.adapters
package. This is poor modular design
+on the part of JDOM, but we cannot fix it without breaking existing
+clients. Therefore marking the dependencies as optional is the best
+solution.
Even deeper analysis is sometimes required: e.g., do any other classes
+in JDOM refer to OracleV1DOMAdapter
? Such questions are best answered
+by searching the source code of the library. When source code is not
+available then disassembly with the javap tool or decompilation with JAD
+usually helps.
After performing class level used-by analysis, remember to remove any +temporary headers, i.e. TEMP.
+ +Undesirable imports can also result from “dead code”, i.e. code that is
+not reachable from any part of the public API of the library. For
+example libraries sometimes contain JUnit test cases or old classes that
+are no longer used. JDOM does not suffer from such problems, but let us
+suppose for a moment that it did contain an invalid dependency on JUnit.
+We could completely remove the imported org.junit
package by adding an
+exclusion rule to Import-Package
:
Import-Package: \
+ oracle.xml.parser*;resolution:=optional,\
+ org.apache.xerces.*;resolution:=optional,\
+ !org.junit*,\
+ *
+
Exclusion rules should be used with caution, as they can cause the +bundle to produce NoClassDefFoundError. Careful used-by analysis should +be performed to ensure that the dependency really is only relevant to +unreachable code.
+ +In this example bnd file, we do not provide the dependency JARs, bnd therefore does not +have the ability to automatically calculate the correct version of an +import because the target package is usually not visible when +the bundle is created. Therefore bnd generates imports that use the +default import version range, which in OSGi is implicitly “[0, ∞)”, or +“zero to infinity” —in other words, a range that matches any version.
+ +When such a wide version range is used, the bundle will normally work
+initially, but will suffer problems when the API of the imported package
+evolves. For example, a major new version of the imported API may
+introduce breaking changes, but our bundle will still resolve against
+it. This could result in errors such as IncompatibleClassChangeError
,
+AbstractMethodError
, NoSuchMethodError
, etc. Therefore we should
+manually add import ranges where possible.
For example the JDOM version we are wrapping has been built against
+version 1.1.2. The Jaxen imports can be refined by adding the following
+Import-Package
statement to jdom.xpath.bnd
:
Import-Package: \
+ org.jaxen.*;version="[1.1,2)",\
+ *
+
Note the import range 1.1 through 2, exclusive of 2 — this is in +compliance with OSGi Semantic Versioning guidelines. The +API library may not follow the OSGi guidelines so sometimes an +alternative range may be required.
+ +Often our biggest problem is working out which version of a dependency +library was used to build the library we are wrapping. In the case of +JDOM, we can find jaxen.jar in the lib directory of its source project +and note that its manifest indicates version 1.1.2. If the project is +built with Apache Maven we can usually find a version in the POM. Other +times we must resort to reading project documentation, if it exists.
+ +Note that version ranges cannot be added for JRE packages, e.g.
+javax.swing
or org.xml.sax
because the Java specifications do not
+define the version of any of these packages, and therefore the OSGi
+framework exports them all as version 0.0.0. As an alternative, add a
+Bundle-RequiredExecutionEnvironment
header to indicate the basic Java
+level required by the bundle:
Bundle-RequiredExecutionEnvironment: J2SE-1.5
+
Other possible values include JavaSE-1.6, OSGi/Minimum-1.0, etc.
+ +Bnd discovers package dependencies in a bundle by scanning the bytecode
+of the compiled Java files. This process finds all of the static
+dependencies of the Java code, but it does not discover dynamic
+dependencies, for example those arising from the use of
+Class.forName()
. There is no generic way for Bnd to calculate all
+dynamic dependencies. However there are certain well-known configuration
+formats that result in dynamic dependencies, and Bnd can analyse these
+formats through the use of plugins.
For example, some bundles use the Spring Framework for dependency +injection. Spring uses XML files that refer to fully qualified Java +class names:
+ +<bean id="myBean" class="org.example.beans.MyBean">
+</bean>
+
Here the org.example.beans
package is a dependency of the bundle that
+should be added to Import-Package
. Bnd can discover this dependency by
+adding a Spring analyser plugin via a declaration in the descriptor
+file:
-plugin: aQute.lib.spring.SpringComponent
+
Similar plugins exist for JPA and Hibernate, and custom plugins can be +written to support other configuration formats or scripting languages.
+ +In summary the process of wrapping a JAR as an OSGi bundle is as +follows:
+ +Create the bnd descriptor, using the template in Appendix A.
+Generate the initial wrapping and review the imports for suspicious +dependencies.
+Fix problematic dependencies using the following heuristics:
+The following template bnd descriptor can be used for the initial +wrapping. The placeholders on the first three lines must be filled in:
+ +-classpath: <INPUT JAR(S)>
+Bundle-SymbolicName: <NAME>
+ver: <VERSION>
+-output: ${bsn}-${ver}.jar
+Bundle-Version: ${ver}
+Export-Package: *;version=${ver}
+# Uncomment next line to customize imports. The last entry MUST be "*"
+# Import-Package: *
+
Bnd can help you to maintain your projects’ documentation up-to-date. +In a best effort, Bnd can automatically update your documentation according to changes in your programs. +Whenever it is not possible, you still have an easy way to edit the documentation manually.
+ +The process is in two steps: first, Bnd will analyze your projects and your bundles to
+collect and aggragate relevant data into an intermediate format (json
or xml
). Then,
+it will process the data with a template file (xslt
or twig
) to generate the documentation.
Relevant data are the OSGi headers, the configuration of your services, code snippets that you write +with the help of your IDE, your Gogo commands, etc. Once they are collected you have three ways to use them:
+ +This feature is available for the Bnd Workspace Model and Maven projects respectivly using Bnd CLI and bnd-reporter-maven-plugin
. We will use
+Bnd CLI as an example in the next sections, you can directly look at the documentation in the Github repository for the corresponding feature with Maven.
++ +Note: It is necessary that the workspace is completely built before generating the documentation files to take into account the latest changes.
+
Custom documentations are configured with the -exportreport instruction. +This instruction defines a list of reports which can then be exported with the exportreport export subcommand.
+ +For example, executing bnd exportreport export
on a project with the following configuration:
bnd.bnd
+ +-exportreport: metadata.json, info.html;template=https://.../mytemplate.xslt
+
will export two files: metadata.json
(intermediate data) and info.html
(final documentation file).
With the exportreport readme subcommand you can generate a set of README files.
+If this command is applied on a workspace, a readme.md
file will be generated into the workspace folder as well as one
+readme.md
file for each project. If a project builds multiple bundles, additional readme.<bsn>.md
will be generated for each
+bundle built.
In case you want to customize the README files with your own text, you have to create a readme.twig
file next to the readme.md
file that you wish to customize.
The simplest way to proceed is to copy-paste the following snippet into this file:
+ +{% embed 'default:readme.twig' %}
+{% block beforeTitle %}
+Write your own markdown text here.
+{% endblock %}
+{% endembed %}
+
Inside an embed
tag one can only specify block tags which will override the parent template included.
+Here, the parent template is the readme template file built-in into Bnd available at the default:readme.twig
URL (see the file here). This file defines a set of blocks that you can override with your own text.
+In the above snippet, we override the beforeTitle
block. You can add multiple blocks depending on where you want to insert your text, here is a list of available blocks:
beforeTitle
, afterTitle
beforeOverview
, afterOverview
beforeLinks
, afterLinks
beforeCoordinates
, afterCoordinates
beforeArtifacts
, afterArtifacts
beforeCodeUsage
, afterCodeUsage
beforeComponents
, afterComponents
beforeDevelopers
, afterDevelopers
beforeLicenses
, afterLicenses
beforeCopyright
, afterCopyright
beforeVendor
, afterVendor
before*
blocks are shown between the title of a section and the generated content. after*
blocks are shown at the end of a section, after the generated content.
Name | +Description | +Default value | +
---|---|---|
iconUrl |
+ URL to an icon that will be shown in front of the title | +”” | +
The command line tool can be invoked in several different ways:
+ +In this text bnd
is used as if it is a command line program. This should be set up as:
java -jar
+ | !General Option | ++ | !Description | ++ |
+ | -failok | ++ | Same as the property -failok. The current run will create a JAR file even if there were errors. | ++ |
+ | -exceptions | ++ | Will print the exception when the software has ran into a bad exception and bails out. Normally only a message is printed. For debugging or diagnostic reasons, the exception stack trace can be very helpful. | ++ |
[ -f, --full ] - Do full
+[ -p, --project <string> ] - Identify another project
+[ -t, --test ] - Build for test
+[ -o, --output <string> ] - Specify the output file path. The default is
+ output.jar in the current directory
+
page | +Description | +Class | + + + +
---|---|---|
create | +Create a JAR | ++ |
baseline [options] <[newer jar]> <[older jar]> | +Compare a newer bundle to a baselined bundle and provide versioning advice. | ++ |
bash | +Generate autocompletion file for bash | ++ |
bnd | +The swiss army tool for OSGi | ++ |
bootstrap | +Interactive gogo shell | ++ |
bsn2url | +From a set of bsns, create a list of urls if found in the repo | ++ |
build [options] | +Build a project. This will create the jars defined in the bnd.bnd and sub-builders. | ++ |
buildx [options] | +Build project, is deprecated but here for backward compatibility. If you use it, you should know how to use it so no more info is provided. | ++ |
bump [options] <<major|minor|micro>> | +Bumps the version of a project. Will take the current version and then increment with a major, minor, or micro increment. The default bump is minor. | ++ |
changes [options] | +Show the changes in this release of bnd | ++ |
clean [options] ... | +Clean a project | ++ |
convert [options] <[from]> <[to]> | +Converter to different formats | ++ |
action [options] ... | +Equivalent jar command c[v0mf] command (supports the jar tool's syntax). Will wrap the bundle unless --wrapnot is specified | ++ |
debug [options] ... | +Show a lot of info about the project you're in | ++ |
defaults | ++ | + |
deliverables [options] | +Show all deliverables from this workspace. with their current version and path. | ++ |
diff [options] <[newer file]> <[older file]> | +Compares two jars. Without specifying the JARs (and when there is a current project) the jars of this project are diffed against their baseline in the baseline repository, using the sub-builder's options (these can be overridden). If one JAR is given, the tree is shown. Otherwise 2 JARs must be specified and they are then compared to each other. | ++ |
digest [options] <[file...]> | +Digest a number of files | ++ |
do [options] ... | +Execute a file based on its extension. Supported extensions are bnd (build), bndrun (run), and jar (print) | ++ |
eclipse pde [options] <[repo-dir]> <[...]> | +Import PDE projects into a bnd workspace | ++ |
eclipse [options] | +Show info about the current directory's eclipse project | ++ |
ees <[jar-file]...> | +Show the Execution Environments of a JAR | ++ |
exportreport <sub-cmd> [options] | +Generate and export reports of a workspace, a project or of a Jar. | ++ |
extract [options] ... | +Extract files from a JAR file, equivalent jar command x[vf] (syntax supported) | ++ |
find [options] <[file]...> | +Go through the exports and/or imports and match the given exports/imports globs. If they match, print the file, package and version. | ++ |
generate | +Generate autocompletion file for bash | ++ |
grep [options] <[pattern]> <[file]...> | +Grep the manifest of bundles/jar files. | ++ |
identity | ++ | + |
index [options] <[bundles]...> | +Index bundles from the local file system | ++ |
info [options] | +Show key project variables | ++ |
junit [options] <testclass[:method]...> | +Test a project according to an OSGi test | ++ |
macro [options] <[macro]> <[...]> | +Show macro value. Macro can contain the { and } parentheses but it is also ok without. You can use the ':' instead of the ';' in a macro | ++ |
maven ( 'settings' | 'bundle' | +Special maven commands | ++ |
package [options] <<bnd|bndrun>> <[...]> | +Package a bnd or bndrun file into a single jar that executes with java -jar <>.jar. The JAR contains all dependencies, including the framework and the launcher. A profile can be specified which will be used to find properties. If a property is not found, a property with the name [<profile>]NAME will be looked up. This allows you to make different profiles for testing and runtime. | ++ |
plugins [options] | +Execute a Project action, or if no parms given, show information about the project | ++ |
print [options] <[jar-file]...> | +Provides detailed view of the bundle. It will analyze the bundle and then show its contents from different perspectives. If no options are specified, prints the manifest. | ++ |
project [options] | +Execute a Project action, or if no parms given, show information about the project | ++ |
release [options] | +Release this project | ++ |
repo [options] <[sub-cmd]> ... | +Access to the repositories. Provides a number of sub commands to manipulate the repository (see repo help) that provide access to the installed repos for the current project. | ++ |
run [options] <[bndrun]> | +Run a project in the OSGi launcher. If not bndrun is specified, the current project is used for the run specification | ++ |
runtests [options] ... | +Run OSGi tests and create report | ++ |
schema [options] ... | +Print out the packages from spec jars and check in which ees they appear. Very specific. For example, schema ee.j2se-1.6.0 ee.j2se-1.5.0 ee.j2ee-1.4.0 | ++ |
select [options] <[jar-path]> <[...]> | +Helps finding information in a set of JARs by filtering on manifest data and printing out selected information. | ++ |
settings [options] <[key][=<[value]>]...> | +Set bnd global variables. The key can be wildcarded. | ++ |
shell [options] | +Open a shell on a project, workspace, or plain bnd defaults and exercise commands and macros | ++ |
source [options] <[jar path]> <[source path]> | +Merge a binary jar with its sources. It is possible to specify source path | ++ |
sync [options] | +Execute a Project action, or if no parms given, show information about the project | ++ |
syntax [options] <header|instruction> ... | +Access the internal bnd database of keywords and options | ++ |
test [options] <testclass[:method]...> | +Test a project according to an OSGi test | ++ |
type [options] ... | +List files int a JAR file, equivalent jar command t[vf] (syntax supported) | ++ |
verify <[jar path]> <[...]> | +Verify jars | ++ |
version [options] | +Show version information about bnd | ++ |
view [options] <[jar-file]>> <[resource]> <[...]> | +View a resource from a JAR file. Manifest will be pretty printed and class files are shown disassembled. | ++ |
wrap [options] <[jar-file]> <[...]> | +Wrap a jar into a bundle. This is a poor man's facility to quickly turn a non-OSGi JAR into an OSGi bundle. It is usually better to write a bnd file and use the bnd <file>.bnd command because that has greater control. Even better is to wrap in bndtools. | ++ |
xmlrepodiff [options] <newer XML resource repository> <older XML resource repository> | +Compares two XML resource repositories | ++ |
xref [options] <[jar path]> <[...]> | +Show a cross references for all classes in a set of jars. | ++ |
It is quite easy to use bnd from Java, you only need to include biz.aQute.bndlib on your class path. This chapter shows you some samples of how to use bndlib.
+ +By default, bnd creates a container with resources and then calculates the manifest. However, these phases are separated although they use the same instructions. The following snippet therefore shows how you can create a manifest from an existing file or directory.
+ +Analyzer analyzer = new Analyzer();
+Jar bin = new Jar( new File("bin") ); // where our data is
+analyzer.setJar( bin ); // give bnd the contents
+
+// You can provide additional class path entries to allow
+// bnd to pickup export version from the packageinfo file,
+// Version annotation, or their manifests.
+analyzer.addClasspath( new File("jar/spring.jar") );
+
+analyzer.setProperty("Bundle-SymbolicName","org.osgi.core");
+analyzer.setProperty("Export-Package",
+ "org.osgi.framework,org.osgi.service.event");
+analyzer.setProperty("Bundle-Version","1.0");
+
+// There are no good defaults so make sure you set the
+// Import-Package
+analyzer.setProperty("Import-Package","*");
+
+// Calculate the manifest
+Manifest manifest = analyzer.calcManifest();
+
+public interface MakePlugin {
+/**
+ * This plugin is called when Include-Resource detects
+ * a reference to a resource that it can not find in the
+ * file system.
+ *
+ * @param builder The current builder
+ * @param source The source string (i.e. the place
+ * where bnd looked)
+ * @param arguments Any arguments on the clause in
+ * Include-Resource
+ * @return A resource or null if no resource
+ * could be made
+ * @throws Exception
+ */
+Resource make(Builder builder, String source,
+ Map<String,String> arguments) throws Exception;
+}
+
Make plugins kick in when the Include-Resource
header tries to locate a resource but it cannot find that resource. The -make
option defines a number of patterns that are mapped to a make instruction.
For example, if you have
+ +Include-Resource: com.acme.Abc.ann
+
If no such resource is found, bnd will look in the -make instruction. This instruction associates a pattern with a plugin type. For example:
+ +-make: (*.jar); type=xyz; abc=3; def="$1"
+
The first name part of the clause is matched against the unfound resource. All plugins are called sequentially until one returns non-null. The arguments on the -make clause are given as parameters to the make plugin. Normally all Make Plugins should verify the type field.
+ +bnd has a bnd and a copy Make Plugin.
+ +Bnd and Bndtools runs on any system that can run Eclipse. However, every operating system is different and has its own quirks. Windows in particular has a few that impact developers using Bnd. While the Bnd experience may never be as smooth on Windows as it is on macOS or Linux, there are a few tweaks that make it a lot smoother than what comes out of the box.
+ +It is possible that some of the tips listed here will also help for Eclipse development work in general, or perhaps any development work on Windows.
+ +Windows has two key architectural differences to *nix-based OSes that really impact development work in Java:
+ +Most *nix systems use advisory file locking, rather than mandatory locking. Windows uses mandatory locking rather than advisory. Most Java programs are developed on *nix and tend to assume *nix locking semantics. This means that they are not as careful at cleaning up after themselves and ensuring that file locks are released.
+ +In particular, there are problems when an application exits using System.exit()
- the normal way for Java applications to clean up is to release a lock is in a finally
clause. However, when an application calls System.exit()
, these normal channels are bypassed, which means the JVM never gets a chance to release the locks and hence can’t delete the locked files.
From version 10, Windows’ built-in anti-malware program Windows Defender has become very tightly integrated with the OS - seemingly, it is written into the Windows kernel API IO functions themselves, which means it’s nearly impossible to bypass. This makes it extremely well suited to catching malware in the act. Unfortunately, it means it slows down every file write. When you are writing lots of small files, this delay is significant.
+ +Prior to Bnd 5.0, there was a bug that caused the launcher to pause for several seconds if you had -runtrace
enabled. This bug has been fixed as of 5.0 (currently in pre-release at the time of writing), so it is worthwhile upgrading to use 5.0 if you are able.
Due to file locking issues, Bnd on Windows will always copy bundles into a newly launched application rather than referencing them (see -runnoreferences
). This copy can run to a few hundred megabytes.
Again due to file locking (and its overuse of System.exit()
), Bnd is not very good at cleaning up temporary files on Windows after it exits. So usually, the application folders are left behind after the launched application exits.
A few hundred megabytes per launch starts to add up, and if you’re running off a relatively small SSD you can find yourself running out of disk space.
+ +It is recommended that you periodically clear out these files from your temp directory. By default, the temp directory is in %UserHome%\AppData\Local\Temp
. OSGi temp dirs created by Felix typically have the pattern: osgi.nnnnnnnnnnnnnnnnn.fw
. Depending on which OSGi framework and which Java utilities you are using in your app, there may be others to look out for - if you are using a custom temp dir (see below), then you can simply clear out the entire directory.
You can turn off real-time malware protection in Windows settings. However, Windows 10 will only allow this situation to persist for a short time (perhaps 24 hours) before automatically turning it back on again. Also, this approach leaves your entire system without any real-time protection.
+ +A better solution is to disable antivirus scanning for specific directories. You can find instructions on how to do this here. For maximum benefit, you should exclude:
+%UserHome%\AppData\Local\Temp
).
+However, excluding your temp directory might be a bit of a security hole as it is likely to be a place where malware will try and write first. If you are concerned about this, see the next section
+for a workaround.It is possible that excluding other directories may help performance, as Windows Defender +seems to take a keen interest in jar files. Some examples:
+.m2
directory (usually found in your home directory);%UserHome%\.p2
directory if you have installed it using
+the P2 bundle pool.Be wary that each additional exclusion you add increases the security risk. You need to weigh the performance benefits of excluding each folder vs the increase in security risk.
+ +As noted above, for best performance you should exclude the temp directory, however for best safety you may not wish to. A good compromise is:
+ +C:\temp
).-Djava.io.tmpdir=<path to tmp>
vm arg to the eclipse.ini
file..bndrun
files to use this temp directory by using the -runvm
instruction (eg: -runvm: -Djava.io.tmpdir=C:\\temp
- noting the double-backslash).As an added bonus, this makes cleaning up the remaining temp files much easier as you can safely delete the entire directory, knowing that no other applications are using the directory.
+ +If you’ve got an SSD, the above tips should suffice to get good performance. On one test, launching the Bndtools test Eclipse instance itself (with over 200 bundles) took ~2s to install all the bundles for a running Eclipse instance.
+ +However, if you’re using a slower hard disk and you have enough RAM (at least 8GB), you may also consider configuring a small RAM disk of around 1GB and putting your temp directory on the RAM disk. There are a few free RAM disk utilities available for Windows.
+ +You can also easily clear out the accumulated temp files by unmounting and re-mounting the RAM disk.
+ +If you’re a developer of a tool that needs to be listed here, do not hesitate to submit a pull request at github.
+ + +The subsequent sections provide the reference part of the manual. This consists of the following:
+ +-
(minus sign).Since bnd is a library, it gets used in many different places. This makes some of the headers, instructions, and/or +macros only applicable in a specific context. This is generally indicated on a page with a class button on the right +side. For example:
+ +The following classes are used:
+ +page | +Description | +Class | + + + +
---|---|---|
Bnd-AddXmlToTest RESOURCE ( ',' RESOURCE ) | +Add XML resources from the tested bundle to the output of a test report. | +Macro | +
Bnd-LastModified LONG | +Timestamp from bnd, aggregated last modified time of its resources | +Header | +
Bundle-ActivationPolicy ::= policy ( ';' directive )* | +The Bundle-ActivationPolicy specifies how the framework should activate the bundle once started. | +Header | +
Bundle-Activator CLASS | +The Bundle-Activator header specifies the name of the class used to start and stop the bundle | +Header | +
Bundle-Blueprint RESOURE (',' RESOURCE ) | +The Bundle-Activator header specifies the name of the class used to start and stop the bundle | +Header | +
Bundle-Category STRING (',' STRING ) | +The categories this bundle belongs to, can be set through the BundleCategory annotation | +Header | +
Bundle-ClassPath ::= entry ( ',' entry )* | +The Bundle-ClassPath header defines a comma-separated list of JAR file path names or directories (inside the bundle) containing classes and resources. The full stop ('.' \u002E) specifies the root di- rectory of the bundle's JAR. The full stop is also the default | +Header | +
Bundle-Contributors ... | +Lists the bundle contributors according to the Maven bundle-contributors pom entry | +Header | +
Bundle-Copyright STRING | +The Bundle-Copyright header contains the copyright specification for this bundle. Can be set with the BundleCopyright annotation. | +Header | +
Bundle-Description STRING | +The Bundle-Description header defines a short description of this bundle.. | +Header | +
Bundle-Developers ... | +Lists the bundle developers according to the Maven bundle-developers pom entry | +Header | +
Bundle-DocURL STRING | +The Bundle-DocURL headers must contain a URL pointing to documentation about this bundle. | +Header | +
Bundle-License ::= '<<[EXTERNAL]>>' | ( license ( ',' license ) * ) | +The Bundle-License header provides an optional machine readable form of license information. | +Header | +
Bundle-ManifestVersion ::= 2 | +The Bundle-ManifestVersion is always set to 2, there is no way to override this. | +Header | +
Bundle-Name STRING | +The Bundle-Name header defines a readable name for this bundle. This should be a short, hu- man-readable name that can contain spaces. | +Header | +
Bundle-NativeCode ::= nativecode ( ',' nativecode )* ( ',' optional ) ? | +The Bundle-NativeCode header contains a specification of native code libraries contained in this bundle. | +Header | +
Bundle-RequiredExecutionEnvironment ::= ee-name ( ',' ee-name )* | +The Bundle-RequiredExecutionEnvironment contains a comma-separated list of execution environ- ments that must be present on the OSGi framework. See Execution Environment on page 44. This header is deprecated. | +Header | +
Bundle-SymbolicName ::= symbolic-name ( ';' parameter ) * | +The Bundle-SymbolicName header specifies a non-localizable name for this bundle. The bundle symbolic name together with a version must identify a unique bundle though it can be installed multiple times in a framework. The bundle symbolic name should be based on the reverse domain name convention, s | +Header | +
Bundle-Version ::= version | +The Bundle-SymbolicName header specifies a non-localizable name for this bundle. The bundle symbolic name together with a version must identify a unique bundle though it can be installed multiple times in a framework. The bundle symbolic name should be based on the reverse domain name convention. | +Header | +
Conditional-Package PACKAGE-SPEC (',' PACKAGE-SPEC) * | +Recursively add packages from the class path when referred and when they match one of the package specifications. | +Header | +
Created-By STRING | +Java version used in build | +Header | +
DynamicImport-Package ::= dynamic-description ( ',' dynamic-description )* | +The DynamicImport-Package header contains a comma-separated list of package names that should be dynamically imported when needed. | +Header | +
Export-Package ::= export ( ',' export)* | +The Export-Package header contains a declaration of exported packages | +Header | +
Fragment-Host ::= bundle-description | +The Fragment-Host header defines the host bundles for this fragment. | +Header | +
Import-Package ::= import ( ',' import )* | +The Import-Package header declares the imported packages for this bundle. | +Header | +
Include-Resource ... | +Includes resources, see -includeresource | +Header | +
Meta-Persistence ::= ( RESOURCE ( ',' RESOURCE )* )? | +A Persistence Bundle is a bundle that contains the Meta-Persistence header. If this header is not present, then this specification does not apply and a JPA Provider should ignore the corresponding bundle. | +Header | +
Private-Package PACKAGE-SPEC ( ',' PACKAGE-SPEC )* | +Specifies what packages to include | +Header | +
Provide-Capability ::= capability (',' capability )* | +Specifies that a bundle provides a set of Capabilities, | +Header | +
Require-Bundle ::= bundle-description ( ',' bundle-description )* | +The Require-Bundle header specifies that all exported packages from another bundle must be im- ported, effectively requiring the public interface of another bundle. | +Header | +
Require-Capability ::= requirement ( ',' requirement )* | +Specifies that a bundle requires other bundles to provide a capability | +Header | +
Service-Component ::= RESOURCE ( ',' RESOURCE ) | +XML documents containing component descriptions must be specified by the Service-Component header in the manifest. | +Header | +
Test-Cases CLASS ( ',' CLASS ) * | +Header to automatically execute tests in the bnd JUnit tester | +Header | +
Tool STRING | +Bnd version used to build this bundle | +Header | +
A bnd instruction is a property that starts with a minus sign (‘-‘). An instruction instructs bndlib to do something, in general providing parameters to the code. All instructions in bndlib are listed later in this page.
+ +Almost all bndlib instructions follow the general OSGi syntax. However, the bndlib syntax is in general a bit more relaxed, for example you can use either single quotes (‘’’) or double quotes (‘”’) while OSGi only allows double quotes. Dangling comma’s and some other not exactly correct headers are accepted by bndlib without complaining. Values without quotes are accepted as long as they cannot confuse the general syntax. For example, a value like 1.20 does not have to be quoted since the period (‘.’) cannot confuse the parser on that place, however, a value like [1.3,4) must be quoted since it contains a comma.
+ +In this mannual we adhere to the same conventions as the OSGi specification. This is mostly reflected in the names for the terminals in the syntax specifications. As a reminder we repeat the syntax rules and common terminals:
+ +*
– Repetition of the previous element zero or more times, e.g. ( ',' element ) *
+
– Repetition one or more times?
– Previous element is optional( ... )
– Grouping|
– Or[...]
– Set (one of)..
– Range<...>
– Externally defined token, followed with a description~
– Not, negationThe following terminals are pre-defined:
+ +ws ::= <see Character.isWhitespace>
+digit ::= [0..9]
+alpha ::= [a..zA..Z]
+alphanum ::= alpha | digit
+token ::= ( alphanum | '_' | '-' )+
+number ::= digit+
+jletter ::= <see [JSL][1] for JavaLetter>
+jletterordigit ::= <See [JLS][1] for JavaLetterOrDigit >
+qname ::= <See [JLS][1] for fully qualified class name >
+identifier ::= jletter jletterordigit *
+extended ::= ( alphanum | '_' | '-' | '.' )+
+quoted-string-d ::= '"' ( ~["\#x0D#x0A#x00] | '\"'|'\')* '"'
+quoted-string-s ::= ''' ( ~['\#x0D#x0A#x00] | '\''|'\')* '''
+quoted-string ::= quoted-string-s | quoted-string-d
+special-chars ::= ~["\#x0D#x0A#x00:=;,<See [JLS][1] for whitespace>]
+path ::= special-chars+ | quoted-string
+value ::= quoted-string | path
+argument ::= extended | quoted-string
+parameter ::= directive | attribute
+directive ::= extended ':=' argument
+attribute ::= extended '=' argument
+parameters ::= parameter ( ',' parameter ) *
+unique-name ::= identifier ( '.' identifier )*
+symbolic-name ::= token('.'token)*
+package-name ::= unique-name
+
+version ::= major( '.' minor ( '.' micro ( '.' qualifier )? )? )?
+major ::= number
+minor ::= number
+micro ::= number
+qualifier ::= ( alphanum | '_' | '-' )+
+
+version-range ::= interval | atleast
+interval ::= ( '[' | '(' ) floor ',' ceiling ( ']' | ')' )
+atleast ::= version
+floor ::= version
+ceiling ::= version
+
White spaces between terminals are ignored unless specifically noted. The only exception is the directive, a directive must be connected to the colon (‘:’). That is, bndlib stores attributes and directives in the same map and distinguishes them by the fact that directives end in a colon and attributes do not. For example, using a directive like foo; path := https://www.xyz.com
will not work correctly because the path :
is not a valid attribute nor directive name. Any value that contains a space, a tab, a comma, colon, semicolon, equal sign or any other character that is part of a terminal in the grammar must be quoted.
Almost all bndlib instructions follow the general OSGi syntax for a header.
+ +-instruction ::= parameters
+parameters ::= clause ( ',' clause ) *
+clause ::= path ( ';' path ) *
+ ( ';' parameter ) *
+
There are a number of short-cuts that are regularly used in bnd commands:
+ +list ::= value ( ',' value ) *
+url ::= <URL>
+file-path ::= <file path, using / as separator>
+
Most instructions that accept multiple clauses are merged instructions. A merged instruction is an instruction which is specified by its prefix but which will merge any property that starts with this prefix followed by a period (‘.’). For example, the base instruction -plugin
instruction accepts -plugin.git
as well. The reason for this merging is that allows to append the base instruction with instructions from other files. For example, the bnd file in the cnf/ext
directory are automatically included in the properties of the workspace. Since there can be many files, there would be a need to coordinate the using of a singleton base instruction. As always, singletons suck and in this case we solved it with merged instructions.
Some instructions put, sometimes very subtle, semantics on their ordering. To prevent different results from run to run, we make order the merge properties by placing the base instruction in front and then followed by the values from the other keys in lexically sorted order. For example, -a
, -a.x
, and -a.Z
, and -a.1
will concatenate the clauses in the order -a,-a.1,-a.Z,-a.x
.
Each of the constituents of the merged properties can be empty, this is properly handled by bndlib and will not create a an empty clause as would easily happen when the same thing was done with macros. Empty clauses can help when there is an optionality that depends on some condition.
+ +As an example, lets set the -buildpath
instruction:
-buildpath: com.example.foo;version=1.2
+
+-buildpath.extra: ${if;${debug};com.example.foo.debug\;version=1.2}
+
This will result in a buildpath of (when debug is not false) of: com.example.foo;version=1.2, com.example.foo.debug;version=1.2
.
Instructions can also be decorated. A decorator is a header that ends with +
or ++
. A header like -runbundles
is first merged and then decorated.
In this case, -runbundles
is the stem. First, the total header is assembled by merging the property that has that stem. If there are properties that match stem + +.*
or ++.*
, then these properties are used to decorate the merged property. Notice that for the decorator the root key includes the +
sign, the suffixes must come after the +
sign. For example, for the header foo
, the decorator would be foo+
and that would match a key like foo+.bar
.
The decorator is a Parameters, it consists of a key and a set of attributes. The decorator key is usually a glob expressions.
+ +After the header is merged, the key of each entry is matched against all globs in the decorator following the order of the decorator. When the first match is found, the attributes of the decorator clause that matches are stored with the attributes of the Parameter entry, overriding any attribute with the same attribute key. A Parameter entry key can only match one decorator glob expression.
+ +For example, -runbundles a
and -runbundles+ *;startlevel=20
will result in the content a;startlevel=20
.
If the name of the decorator clause attribute starts with !
, then the attribute, using the attribute name after removing the leading !
, is removed from the Parameter entry. If the name of the decorator clause attribute starts with ~
, then the decorator clause attribute value will not overwrite an existing value of the Parameter entry attribute, using the attribute name after removing the leading ~
.
Example:
+ + -foo a, b, c;skip=true, d
+ -foo+ b;skip=true,c;skip=false
+ -foo+.d d;skip=true
+
In this case, the first entry matched is b
and it is matched against the second entry in the -foo
instruction. Since the decorator has the skip=true
attribute, it is carried over to the instruction. The result is therefore:
a, b;skip=true; c;skip=false, d;skip=true
+
If the decoration ends with 2 plus signs, for example -foo++
, then the literals in the decoration headers will be added to the result if they are not matched to any key in the source header. If the decoration ends with a single +
sign, literals that do not match are ignored.
decorated
that can be used to apply decoration to any property keyIf a value in an instruction has a scope then you will be able to use a selector. A scope is a well defined, finite, set. For example, if you use the ‘-exportcontents’ instruction you can specify the packages that can be exported. Since you can only export the packages that are inside your bundle, the scope is the set of package names that were collected so far.
+ +The syntax for a selector is:
+ +selector ::= '!' ?
+ '=' ?
+ '*' | ( '.' | '?' | '|' | '$' | . | other ) *
+ ( '.*' ) ?
+ ( ':i' | ':o' | ':io' ) ?
+
A selector is an expression that is matched against all members of the scope. For example, com.example.*
will match any package name that starts with com.example
, for example com.example.foo
, as well as com.example
itself. The syntax used to describe the wildcarding is based on a globbing kind model, however, the selector is translated into a regular expression according to some simple rules.
The replacement rules to build the regular expression are as follows:
+ +*
– Is replaced with .*
, matching any number of characters, triggers wildcard match.?
- Is replaced with .?
, matching any character, triggers wildcard match.|
– Is inserted as is, triggers wildcard match.$
– Escaped to \$
..
– The dot is treated as a package segment separator. It is escaped with \.
. However, if the match ends with .*
, the replacement will be (\..*)?
. This triggers a wildcard match.If you want to go ballistic with regular expressions, then you can go ahead. As long as the wildcards are triggered by one of the defined characters, the replaced string will be used as a regular expression.
+ +If the selector ends with :i
then a case insensitive match is requested, this will ignore any case differences.
+If the selector ends with :o
then the selector is marked optional. This means that when Bnd might warn about unused selectors, it will not warn that an optional selector is unused.
+You can specify both flags by ending the selector with :io
.
A selector can be prefixed with an exclamation mark, this indicates that a match must be treated as a removal operation. To understand removal, it is necessary to realize that the selectors are not declarative, the order of the selectors is relevant.
+ +When bndlib has a scope, it will iterator over each element in the scope and then for element it will iterator over each selector. If there is a match on the scope member then it is always removed from the scope, it will not be matched against later selectors. If the selector starts with an exclamation mark then the member is removed. but it is not treated as a result. Ergo, the exclamation mark allows you to remove members before they are actually matched.
+ +For example, you want to match all implementation packages (i.e. they have impl
in their name) except when they start with com.example
. This can be achieved with:
!com.example.*impl*, *.impl.*
+
Last, and also least, you can prefix the selector with an equal sign (‘=’) (after the exclamation mark if one is present). This will signal a literal match with the string, no globbing will be done.
+ +The way the period (‘.’) is treated is kind of special. The reason is that people expect that the selector com.example.*
actually includes the package com.example
itself. In bndlib this has always been the case although the OSGi Alliance later decided to not follow this pattern when there was a need for the globbing of packages.
In this example, the first selector will remove any match and ignore it, the second selector now only sees scope members that are fitting in the earlier pattern.
+ +Selectors are used in virtually any place where there is a reasonable scope. It is a very powerful feature. However, it is also easy to go overboard. If you find you need to use this feature excessively then you are likely overdoing it.
+ +One of the most painful thing in making bndlib run anywhere is handling of files. Even though Java has a decent abstraction of the file system, problems with spaces in file names and backslashes in the wrong place have caused an uneven amount of bugs. Ok, this partially because most bndlib developers tend to use Macs, still no excuse and quite embarrassing.
+ +Since bnd files need to be portable across environments we’ve chosen to use the Unix file separator, the slash (or solidus ‘/’) for more reasons than I can reasonably sum up here (what was Bill smoking when he picked the reverse solidus for file separator!). In bndlib, all file paths (ok, should) always go through a single method that actually parses it and turns it into a Java File object. This allows us to support a number of features in a portable way. The syntax of a file path is therefore:
+ +file ::= ( '~/' | '/' )? ( ~['/']+ '/' ) * ~['/'] *
+
If a file path starts with:
+ +You can use the ‘..’ operator for a segment in the file path to indicate a parent directory. It is highly advised to always use relative addressing to keep your workspace portable. However, there are a number of macros that can be used as anchors:
+ +${p}
, `${project} – The directory of the current project${workspace}
– The directory of the current workspace${build} – The directory of the
cnf` directory.A FILESPEC
defines a set of files relative to a starting point. It can be identical to a FILE for a single file but it has some special syntax to recurse directories. For example, the FILESPEC foo/bar/**/spec/*.ts
finds all files that end in spec/*.ts
somewhere below the foo/bar
directory.
The syntax is as follows:
+ +FILESPEC ::= filespec ( ',' filespec )*
+filespec ::= ( segment '/' ) filematch
+segment ::= '**' | GLOB
+filematch ::= '**' | '**' GLOB | GLOB
+
If a segment is **
then it will match any directory to any depth. Otherwise it must be a GLOB expression, which can also be a literal. The last segment is the filematch
. This is a GLOB expression on a file name. As a convenience, if it is **
, any file will match in any directory and if it starts with something like **.ts
then it will also recurse and match on *.ts
. That is, the following rules apply to the filematch
:
prefix/** prefix/**/*
+prefix/**.ts prefix/**/*.ts
+
A PATH is a repository specification. It is a specification for a number of JARs and/or bundles from a repository (well, most of the time). A PATH has the following syntax:
+ +PATH ::= target ( ',' target ) *
+target ::= ( entry | FILE ';' 'version' '=' 'file' )
+ ( ';' PARAMETERS ) *
+entry ::= symbolic-name ( ';' version ) ?
+version ::= 'version' '='
+ ( RANGE | 'latest' | 'project')
+
A PATH defines a number of bundles with its entries. Each entry is either a FILE or a symbolic-name. If only a symbolic name is specified, the default will be the import range from 0.0.0. This selects the whole repository for the given symbolic name. Outside the default, the repository entry can be specified in the following ways:
+ +latest
– Use the project’s output or, if the project does not exists, get the bundle with the highest version from the repositories.project
– Mandate the use of the project’s outputA glob is a pattern that can be matched against a candidate to see if it matches. To match multiple candidates with
+one pattern, the pattern is allowed to contain wildcard characters. A wildcard character is a stand in for zero or
+more other characters. For example, a glob like *.xml
matches any name that ends with .xml
, the asterisk *
stands
+in for zero or more characters. Therefore, it matches foo.xml
, xml.xml.xml
, but also just .xml
. The other
+wildchar is the question mark (?
) which matches exactly one character. For example, ???.xml
matches abc.xml
but not a.xml
+or .xml
.
Sometimes it is necessary to match against a number of strings. The vertical bar (|
) can separate these strings. For
+example, abc|def|ghi
matches any of abc
, def
, or ghi
.
It is also possible to group strings inside the pattern using parentheses. For example, foo(a|b)bar
matches fooabar
+or foobbar
.
The glob also supports character classes with the square brackets [
and ]
. Characters between the square brackets
+literally match to any character at that position. These character classes also form a group. For example, foo[abc]bar
matches
+fooabar
, foobbar
, or foocbar
.
Groups can be repeated using a question mark (cardinality 0..1), the asterisk (cardinality 0..n), or the plus sign (cardinality 1..n). For example, (a)*
+matches aaaa
but also the empty string. If the standard cardinalities do not suffice, then a group can be suffixed by a
+cardinality specification using the curly braces ({
and }
). Inside the curly braces the lower bound and the optional upper
+bound can be specified. If no upper bound is specified then the lower bound is also the upper bound.
+For example, (?i)([0-9A-F][0-9A-F]){20}
matches a 40 digit SHA digest in hex regardless of case.
There are some special characters that get special treatment:
+ +special ::= '{' | '}' | '|' | '+' | '*' | '?' | '(' | ')' | '[' | ']'
+
These characters can be escaped with a reverse solidus (\
) to override their special meaning, they will then be matched literally. To
+escape a long string, it is possible to place \Q
at the beginning of a block that needs to be escaped and \E
at the end.
+For example, foo\Q*****\Ebar
Some examples:
+ +Glob | +Regular Expression | +Matches | +
---|---|---|
abc.ts |
+ abc\.ts |
+ abc.ts |
+
*.ts |
+ .*\.ts |
+ abc.ts, def.ts |
+
foo***bar |
+ foo.*.*.*bar |
+ foobar, fooXbar, fooXXXXbar |
+
foo(A|B|C)*bar |
+ foo(A|B|C)*bar |
+ fooAbar, fooBBBAbar,fooCbar |
+
foo\{\}bar |
+ foo\{\}bar |
+ foo{}bar |
+
xx(?i)xx |
+ xx(?i)xx |
+ xxXx , xxXX , xxxx |
+
Some instruction use the GLOB but provide some convenience. For example, the package selectors of Export-Package et. al.
+detect a case like com.example.*
and turn it into a regular expression that matches com.example.foo
but also matches
+com.example
. This is generally explained in the instruction.
This section defines how globs are mapped to regular expressions. Although the globs are quite intuitive to use, +they do expose all regular expression capabilities. A strong advice is to keep it simple. The mapping itself +is actually non-trivial and takes a number of heuristics that can easily go wrong. Basically, if you need this +section you’re likely doing something that is too complex.
+ +For backward compatibility reasons, we support the unix like glob style for or’ing like {a,b,c}
. However, it is
+recommended to use the form using the vertical bar since it is more flexible and closer to regular expressions.
A glob is mapped to a regular expression character by character. During this traversal the following states are kept:
+ +SIMPLE
– Basic simple glob form. This is the default.CURLIES
– Inside a curly braces group that is not a cardinality specification. Curly braces can be nested.QUOTED
– Inside a \Q...\E
block (cannot be nested)BRACKETS
– Inside a character group enclosed by square brackets (cannot be nested).Character sets:
+ +escaped ::= `.` | `$` | `^` | `@` | `%`
+special ::= `?` | `*` | `+`
+end ::= ')' | ']' | '}'
+start ::= '('
+
Escaped characters are escaped in SIMPLE mode and CURLIES mode. In QUOTED or BRACKETS mode no escaping is done. For example
+^$
becomes \^\$
matching literally ^$
. However, [^$]
becomes [^$]
, matching anything but a dollar sign. In QUOTED
+and BRACKETS mode, also the special
, end
, and start
sets are directly inserted without any special handling.
In the SIMPLE and CURLIES mode the special
characters require special handling. For these remaining mappings it is important
+to realize that there are groups. There are parenthesized groups, bracketed group, and curly braces groups. For
+example, (a|b)
, [abcd]
, or {a,b,c}
respectively. If curly braces are used after a group then it is a quantifier and not considered a group. That is, {a,b}
is
+considered a group but [a,b]{1,2}
then the {1,2}
is a quantifier and not a group.
So when a special
character is preceded with a group, it is inserted without any special processing. If it is not
+preceded then the following rules apply:
*
– mapped to .*
, this matches any number of characters?
– mapped to .
, this matches one character+
– mapped to \+
, matches a plus sign!Some examples:
+ +Glob | +Regular Expression | +
---|---|
* |
+ .* |
+
(a)* |
+ (a)* |
+
[abc]+ |
+ [abc]+ |
+
. |
+ \. |
+
[.*+|] |
+ [.*+|] |
+
+ |
+ \+ |
+
(x|y)+ |
+ (x|y)+ |
+
{x,y} |
+ (?:x|y) |
+
{[xa],y} |
+ (?:[xa]|y) |
+
(?:foo) |
+ (?:foo) |
+
{foo} |
+ (?:foo) |
+
[\p{Lower}] |
+ [\p{Lower}] |
+
[a-z&&[^bc]] |
+ [a-z&&[^bc]] |
+
Instruction | +Description | +Class | + + + +
---|---|---|
-augment PARAMETER ( ',' PARAMETER ) * | +Add requirements and capabilities to the resources during resolving. | +Workspace | +
-baseline selector | +Control what bundles are enabled for baselining and optionally specify the baseline version or file. | +Project | +
-baselinerepo qname | +Define the repository to calculate baselining against | +Workspace | +
-builderignore PATH-SPEC ( ',' PATH-SPEC ) * | +List of project-relative directories to be ignored by the builder. | +Project | +
-buildpath PATH | +Provides the class path for building the jar, the entries are references to the repositories. | +Project | +
-buildrepo repo ( ',' repo ) * | +After building a JAR, release the JAR to the given repositories. | +Project | +
-buildtool toolspec (EXPERIMENTAL!) | +A specification for the bnd CLI to install a build tool, like gradle, in the workspace | +bnd | +
-bumppolicy | +The policy for the bump command | +Macro | +
-bundleannotations SELECTORS | +Selects the classes that need processing for standard OSGi Bundle annotations. | +Project | +
-cdiannotations SELECTORS | +Selects the packages that need processing for CDI annotations. | +Project | +
-check 'ALL' | ( 'IMPORTS' | 'EXPORTS' ) * | +Enable additional checking | +Analyzer | +
-classpath FILE (',' FILE) * | +Specify additional file based entries (either directories or JAR files) to add to the used classpath. | +Analyzer | +
-compression DEFLATE | STORE | +Set the compression level for the generated JAR, the default is DEFLATE | +Builder | +
Conditional-Package PACKAGE-SPEC ( ',' PACKAGE-SPEC ) * | +Recursively add packages from the class path when referred and when they match one of the package specifications. | +Project | +
-conditionalpackage PACKAGE-SPEC ( ',' PACKAGE-SPEC ) * | +Recursively add packages from the class path when referred and when they match one of the package specifications. | +Project | +
-conduit | +This project is a front to one or more JARs in the file system | +Project | +
-connection-settings | +Setting up the communications for bnd | ++ |
-consumer-policy VERSION-MASK | +Specify the default version bump policy for a consumer when a binary incompatible change is detected. | +Project | +
-contract | +Establishes a link to a contract and handles the low level details. | +Project | +
-define-contract | +Define a contract when one cannot be added to the buildpath. | +Project | +
-dependson SELECTORS | +Add dependencies from the current project to other projects, before this project is built, any project this project depends on will be built first. | +Project | +
-deploy | +Deploy this project through Deploy plugins (MavenDeploy plugin). Needs work | +Project | +
-deployrepo | +Deploy this project through Deploy plugins (MavenDeploy plugin). Needs work | +Project | +
-diffignore SELECTORS | +Manifest header names and resource paths to ignore during baseline comparison. | +Project | +
-diffpackages SELECTORS | +The names of exported packages to baseline. | +Project | +
-digests DIGEST ( ',' DIGEST ) * | +Set the digest algorithms to use | +Project | +
-distro REPO (',' REPO) | +Resolve against pre-defined system capabilities | +Workspace | +
-donotcopy | +Set the default filters for file resources that should not be copied. | +Project | +
-dsannotations-options SELECTORS | +Options for controlling DS annotation processing. | +Builder | +
-dsannotations SELECTORS | +Selects the packages that need processing for standard OSGi DS annotations. | +Builder | +
-eeprofile 'auto' | PROFILE + | +Provides control over what Java 8 profile to use. | +Project | +
-executable ( rejar= STORE | DEFLATE ) ( ',' strip= matcher ( ',' matcher )* ) ( ',' location= FORMAT ) | +Process an executable jar to strip optional directories of the contained bundles, and/or change their compression. The location string can also be calculated from bsn and version | +Project | +
-export-apiguardian PACKAGE-SPEC, ( ',' PACKAGE-SPEC )* | +Exports the given packages where the the `@API` annotation is found on contained classes. | +Project | +
-export PATH ( ';' PARAMETER )* ( ',' PATH ( ';' PARAMETER )* )* | +Turns a bndrun file into its deployable format | +Project | +
-exportcontents PACKAGE-SPEC, ( ',' PACKAGE-SPEC )* | +Exports the given packages but does not try to include them from the class path. The packages should be loaded with alternative means. | +Project | +
-exportreport report-def ( ',' report-def )* | +Configure a list of reports to be exported. | +Workspace & Project | +
-extension | +A plugin that is loaded to its url, downloaded and then provides a header used instantiate the plugin. | +Project | +
-failok ('true' | 'false')? | +Will ignore any error during building and assume all went ok. | +Project | +
-fixupmessages SELECTOR ( ';' ( is | replace | restrict ) )* ... | +Fixup errors and warnings. | +Project | +
-generate srcs ';output=' DIR ( ';' ( system | generate | classpath))* ... | +Generate sources | +Project | +
-groupid groupId | +Set the default Maven groupId | +Project | +
-include PATH-SPEC ( ',' PATH-SPEC ) * | +Include a number of files from the file system | +Project | +
-includepackage PACKAGE-SPEC, ( ',' PACKAGE-SPEC )* | +Include a number of packages from the class path | +Builder | +
-includeresource iclause | +Include resources from the file system | +Builder & Executable | +
-invalidfilenames | +Specify file/directory names that should not be used because they are not portable. | +Project | +
-javaagent BOOLEAN | +Specify if classpath jars with Premain-Class headers are to be used as java agents | +Project | +
-jpms-module-info-options module-infos+ | +Used to generate the `module-info.class` | +JPMS | +
-jpms-module-info modulename [; version=<version>] [; access=OPEN|SYNTHETIC|MANDATED] | +Used to generate the `module-info.class` | +JPMS | +
-jpms-multi-release BOOLEAN | +Enables generating manifests and module infos for multi release JARs. | +JPMS | +
-launcher | +Options for the runtime launcher | +Project | +
-library library ( ',' library )* | +Apply a bnd library to the workspace, project, or bndrun file | +Workspace or Project | +
-make | +If a resource is not found, specify a recipe to make it. | +Project | +
-manifest FILE | +Override manifest calculation and set fixed manifest | +Builder | +
-manifest-name RESOURCE | +Set the resource path to the manifest, for certain standards the manifest has a different name. | +Ant | +
-maven-dependencies* entry ( ',' entry )* | +Configure maven dependency information for the generated pom | +Project | +
-maven-release ('local'|'remote') ( ',' option )* | +Set the Maven release options for the Maven Bnd Repository | +Project | +
-maven-scope dependency-scope | +Set the default Maven dependency scope to use when generating dependency information in the generated pom | +Project | +
-metatypeannotations-options SELECTORS | +Restricts the use of Metatype Annotation to a minimum version. | +Builder | +
-metatypeannotations SELECTORS | +Selects the packages that need processing for standard OSGi Metatype annotations. | +Builder | +
-namesection RESOURCE-SPEC ( ',' RESOURCE-SPEC ) * | +Create a name section (second part of manifest) with optional property expansion and addition of custom attributes. Patterns not ending with \"/\" target resources. Those ending with \"/\" target packages. | +Builder | +
-nobuildincache BOOLEAN | +Do not use a build in cache for the launcher and JUnit. | +Builder | +
-nobundles BOOLEAN | +Do not build the project. | +Builder | +
-noclassforname BOOLEAN | +Do not add package reference to classes loaded with Class.forName(String). | +Builder | +
-nodefaultversion BOOLEAN | +Do not add a default version to exported packages when no version is present. | +Builder | +
-noee BOOLEAN | +Donot add an automatic requirement on an EE capability based on the class format. | +Ant | +
-noextraheaders BOOLEAN | +Do not add a any extra headers specific for bnd. | +Builder | +
-noimportjava BOOLEAN | +Do not import java.* packages. | +Analyzer | +
-nojunit BOOLEAN | +Indicate that this project does not have JUnit tests | +Ant | +
-nojunitosgi BOOLEAN | +Indicate that this project does not have JUnit OSGi tests | +Ant | +
-nomanifest BOOLEAN | +Do not safe the manifest in the JAR. | +Ant | +
-noparallel CATEGORY;task=TASKS | +Prevent Gradle tasks in the same category from executing in parallel. | +Workspace | +
-nouses BOOLEAN | +Do not calculate uses directives on package exports or on capabilities. | +Project | +
-output FILE | +Specify the output directory or file. | +Analyzer | +
-outputmask TEMPLATE ? | +If set, is used a template to calculate the output file. It can use any macro but the ${@bsn} and ${@version} macros refer to the current JAR being saved. The default is bsn + ".jar". | +Project | +
-package | +Packaging options | +Project | +
-pedantic BOOLEAN | +Warn about things that are not really wrong but still not right. | +Processor | +
-plugin.* plugin-def ( ',' plugin-def )* | +Load plugins and their parameters. | +Processor | +
-pluginpath* PARAMETERS | +Define JARs to be loaded in the local classloader for plugins. | +Processor | +
-pom BOOLEAN | PROPERTIES | +Generate a maven pom in the JAR | +Processor | +
-prepare makespec ( ',' makespec )* | +Execute a number of shell commands before every build (might not work on Windows) | +Project | +
-preprocessmatchers SELECTOR | +Specify which files can be preprocessed | +Builder | +
-privatepackage PACKAGE-SPEC | +Specify the private packages, these packages are included from the class path. Alternative to Private-Package, this version is not included in the manifest. | +Builder | +
-profile KEY | +Sets a prefix that is used when a variable is not found, it is then re-searched under "[<[profile]>]<[key]>". | +Builder | +
-provider-policy VERSION-MASK | +Specify the default version bump policy for a provider when a binary incompatible change is detected. | +Project | +
-releaserepo* NAME ( ',' NAME ) * | +Define the names of the repositories to use for a release | +Project | +
-remoteworkspace (true|false) | +Enable the workspace to server remote requests from the local system, needed for Launchpad | +Project | +
-removeheaders KEY-SELECTOR ( '.' KEY-SELECTOR ) * | +Remove matching headers from the manifest. | +Project | +
-reportconfig plugin-def ( ',' plugin-def )* | +Configure a the content of report. | +Workspace & Project | +
-reportnewer BOOLEAN | +Report any entries that were added to the build since the last JAR was made. | +Project | +
-reproducible BOOLEAN | TIMESTAMP | +Ensure the bundle can be built in a reproducible manner. | +Builder | +
-require-bnd (FILTER ( ',' FILTER )* )? | +The filter can test against 'version', which will contain the Bnd version. If it does not match, Bnd will generate an error. | +Project | +
-resolve.effective qname (',' qname ) | +Set the use effectives for the resolver | +Workspace | +
-resolve.excludesystem true|false | +A property used by the resolver, if set to true (default) it excludes the system resource | +Runtime | +
-resolve (manual|auto|beforelaunch|batch|cache) | +Defines when/how resolving is done to calculate the -runbundles | +Workspace | +
-resolve.preferences qname ( ',' qname ) | +Override the default order and selection of repositories | +Workspace | +
-resolve.reject ( '@'? namespace ( ';filter:=' FILTER )? ), | +Controls rejection of capabilities during resolving. | +Workspace | +
-resolvedebug INTEGER | +Display debugging information for a resolve operation | +Workspace | +
-resourceonly BOOLEAN | +Ignores warning if the bundle only contains resources and no classes. | +Project | +
-runblacklist requirement (',' requirement) | +Blacklist a set of bundles for a resolve operation | +Workspace | +
-runbuilds BOOLEAN | +Defines if this should add the bundles build by this project to the -runbundles. For a bndrun file this is default false, for a bnd file this is default true. | +Project | +
-runbundles* REPO-ENTRY ( ',' REPO-ENTRY )* | +Add additional bundles, specified with their bsn and version like in -buildpath, that are installed and started before the project is run. | +Project | +
-runee EE | +Define the runtime Execution Environment capabilities, default Java 6. | +Builder | +
-runenv PROPERTIES | +Specify a JDB port on invocation when launched outside a debugger so the debugger can attach later. | +Project | +
-runframework ( 'none' | 'services' | ANY )? | +Sets the type of framework to run. If 'none', an internal dummy framework is used. Otherwise the Java META-INF/services model is used for the FrameworkFactory interface name. | +Launcher | +
-runframeworkrestart BOOLEAN | +Restart the framework in the same VM if the framework is stopped or updated. | +Project | +
-runfw REPO-ENTRY | +Specify the framework JAR's entry in a repository. | +Launcher | +
-runjdb ADDRESS | +Specify a JDB socket transport address on invocation when launched outside a debugger so the debugger can attach later. | +Project | +
-runkeep true | false | +Decides to keep the framework storage directory between launching | +Project | +
-runnoreferences BOOLEAN | +Do not use the `reference:` URL scheme for installing a bundle in the installer. | +Launcher | +
-runpath REPO-ENTRY ( ',' REPO-ENTRY ) | +Additional JARs for the remote VM path, should include the framework. | +Project | +
-runprogramargs | +Additional arguments for the program invokation. | +Project | +
-runproperties PROPERTIES | +Define system properties for the remote VM. | +Launcher | +
-runprovidedcapabilities | +Extra capabilities for a distro resolve | +Workspace | +
-runrepos REPO-NAME ( ',' REPO-NAME )* | +Order and select the repository for resolving against. The default order is all repositories in their plugin creation order. | +Resolve | +
-runrequires REQUIREMENT ( ',' REQUIREMENT )* | +The root requirements for a resolve intended to create a constellation for the -runbundles. | +Resolve | +
-runstartlevel ( order | begin | step )* | +Assign a start level to each run-bundle after resolving | +Project | +
-runstorage FILE | +Define the directory to use for the framework's work area. | +Project | +
-runsystemcapabilities* CAPABILITY (',' CAPABILITY ) | +Define extra capabilities for the remote VM. | +Launcher | +
-runsystempackages* PARAMETERS | +Define extra system packages (packages exported from the remote VM -runpath). | +Launcher | +
-runtimeout DURATION | ++ | Project | +
-runtrace BOOLEAN | +Trace the launched process in detail | +Launcher | +
-runvm KEYS | +Additional arguments for the VM invocation. Arguments are added as-is. | +Project | +
-savemanifest FILE | +Write out the manifest to a separate file after it has been calculated. | +Builder | +
-sign PARAMETERS | +Report any entries that were added to the build since the last JAR was made. | +Project | +
-snapshot STRING | +String to substitute for "SNAPSHOT" in the bundle version's qualifier | +Project | +
-sourcepath | +List of directory names that used to find sources. | +Builder | +
-sources BOOLEAN | +Include the source code (if available on the -sourcepath) in the bundle at OSGI-OPT/src | +Builder | +
-stalecheck srcs ';newer=' depends ( ';' ( warning | error | command ))* ... | +Perform a stale check of files and directories before building a jar | +Project | +
-standalone repo-spec (, repo-spec ) | +Disconnects the bndrun file from the workspace and defines its on repositories | +Run | +
-strict BOOLEAN | +If strict is true, then extra verification is done. | +Processor | +
-sub FILE-SPEC ( ',' FILE-SPEC )* | +Build a set of bnd files that use this bnd file as a basis. The list of bnd file can be specified with wildcards. | +Builder | +
-systemproperties PROPERTIES | +These system properties are set in the local JVM when a workspace is started. This was mainly added to allow one to set JVM options via system properties. | +Workspace | +
-testcontinuous BOOLEAN | +Do not exit after running the test suites but keep watching the bundles and rerun the test cases if the bundle is updated. | +Test | +
-tester REPO-SPEC | +Species the tester (bundle) that is supposed to test the code. The default is biz.aQute.tester | +Project | +
-testpackages PACKAGE-SPEC ( ',' PACKAGE-SPEC ) | ++ | Project | +
-testpath REPO-SPEC ( ',' REPO-SPEC ) | +The specified JARs from a repository are added to the remote JVM's classpath if the JVM is started in test mode in addition to the -runpath JARs. | +Project | +
-testsources REGEX ( ',' REGEX )* | +Specification to find JUnit test cases by traversing the test src directory and looking for java classes. The default is (.*).java. | +Project | +
-testunresolved BOOLEAN | +Will execute a JUnit testcase ahead of any other test case that will abort if there are any unresolved bundles. | +Project | +
-undertest true | +Will be set by the project when it builds a JAR in test mode, intended to be used by plugins. | +Project | +
-upto VERSION | +Specify the highest compatibility version, will disable any incompatible features added after this version. | +Project | +
-wab FILE ( ',' FILE )* | +Create a Web Archive Bundle (WAB) or a WAR | +Builder | +
-wablib FILE ( ',' FILE )* | +Specify the libraries that must be included in a Web Archive Bundle (WAB) or WAR. | +Builder | +
-workingset PARAMETER ( ',' PARAMETER ) * | +Group the workspace into different working sets | +Workspace | +
A simple macro processor is added to the header processing. Variables allow a single definition of a value, and the use of derivations. Each header is a macro that can be expanded. Notice that headers that do not start with an upper case character will not be copied to the manifest, so they can be used as working variables. Variables are expanded by enclosing the name of the variable in ${<name>}
(curly braces) or $(<name>)
(parenthesis). Additionally, square brackets [], angled brackets <>, double guillemets «», and single guillemets ‹› are also allowed for brackets. If brackets are nested, that is $[replace;acaca;a(.*)a;[$1]]
will return [cac]
.
There are also a number of macros that perform basic functions. All these functions have the following basic syntax:
+ + macro ::= '${' function '}'
+ | '$\[' function '\]'
+ | '$(' function ')'
+ | '$<' function '>'
+
+ function ::= name ( ';' argument ) *
+
For example:
+ +version=1.23.87.200109111023542
+Bundle-Version= ${version}
+Bundle-Description= This bundle has version ${version}
+
The default macro pattern is the ${...}
pattern, a dollar sign (‘$’) followed by a left curly bracket (‘{‘) and closed by a right curly bracket (‘}’). However, since bndlib is often used inside other systems it also supports alternative macro patterns:
$(...)
,$<...>
,$[...]
,$«..»
(pointing double angle quotation mark \u00AB abd \u00BB), and$‹...›
(single pointing angle quotation mark)@Since(“2.3”) Macros can contain arguments. These arguments are available in the expansion as ${0} to ${9}. ${0} is the name of the macro, so the actual arguments start at 1. The name is also available as ${@}. The arguments as an array (without the name of the macro) is available as ${#}. The more traditional * could not be used because it clashes with wildcard keys, it means ALL values.
+ +For example:
+ +foo: Hello ${1} -> ${foo;Peter} -> "Hello Peter"
+
Keys can be wildcarded. For example, if you want to set -plugin from different places, then you can set the plugin.xxx
properties in different places and combine them with -plugins= ${plugins.*}
.
The ./
sequence is automatically expanded to the current filename when found in a macro source file. This generally what you want but unfortunately not always. The ./
prefix is only replaced when:
So, do you really require the ./
without expansion? If so, then there are the following solutions. The first one is to use another macro to break the sequence:
.=.
+Some-Header: ${.}/conf/admin.xml
+
Alternatively there are a couple of macros that return the given value when called appropriately, and thereby break the sequence:
+ +Some-Header-1: ${def;.;.}/conf/jetty/admin.xml
+Some-Header-2: ${uniq;.}/conf/jetty/admin.xml
+Some-Header-3: ${unescape;.}/conf/jetty/admin.xml
+
In many places a header is used to indicate false or true. In those cases we use some heuristics. The header/macro or whatever is false when:
+ +If the value starts with !
and text follows, the !
is removed and the remaining text is interpreted as a boolean and then negated.
In other cases, the value is considered true
.
@TODO
+ + +page | +Description | +Class | + + + +
---|---|---|
apply ';' MACRO (';' LIST)* | +Convert a list to an invoction with arguments | +Macro | +
average (';' LIST )* | +The average of a list, if no members exception is thrown | +Macro | +
base64 ';' FILE [';' LONG ] | +Get the Base64 encoding of a file | +Macro | +
basedir | +Get the basedirectory of this processor | +Processor | +
basename ( ';' FILEPATH ) + | +A list of the basename (the final part) of a set of file paths. | +Macro | +
basenameext ';' PATH ( ';' EXTENSION ) | +The basename of the given path optionally minus a specified extension | +Macro | +
bndversion | +Returns the current running bnd version as full major.minor.micro | +Macro | +
bsn | +Provide the current bsn when a JAR is generated. This can differ from the Project's bsn when there are sub-bundles. | +Analyzer | +
bytes ( ';' LONG )* | +Format bytes | +Macro | +
cat ';' FILEPATH | +The contents of a file | +Macro | +
classes ( ; QUERY ( ; PATTERN )? )* | +A list of class names filtered by a query language | +Analyzer | +
compare STRING STRING | +Compare two strings by using the compareTo method of the String class. | +Macro | +
currenttime | +The current epoch time in long integer format | +Macro | +
decorated ';' NAME [ ';' BOOLEAN ] | +The merged and decorated Parameters object | +Macro | +
def ';' KEY (';' STRING)? | +The value of the specified property name or a default if macro is not defined. The default is an empty string if not specified. | +Macro | +
digest ';' ALGORITHM ';' FILE | +Get a digest of a file | +Macro | +
dir ( ';' FILE )* | +Returns a list of the directories containing each specified file | +Macro | +
driver ( ';' NAME )? | +the driver of the environment (e.g. gradle, eclipse, intellij) | +Workspace | +
ee | +The name of the highest execution environment found in the current JAR | +Analyzer | +
endswith ';' STRING ';' SUFFIX | +Check if the given string ends with the given prefix | +Macro | +
env ';' KEY (';' STRING)? | +The given environment variable or a default if the environment variable is not defined. The default is an empty string if not specified. | +Macro | +
error ( ';' STRING )* | +Raise an error consisting of all concatenated strings | +Macro | +
exporters ';' PACKAGE | +The list of jars that export the given package | +Analyzer | +
exports | +A list if exported packages | +Analyzer | +
extension ';' PATH | +The file extension of the given path or empty string if no extension | +Macro | +
fileuri ';' PATH | +Return a file uri for the specified path. Relative paths are resolved against the domain processor base. | +Macro | +
filter ';' LIST ';' REGEX | +Filters entries in a list that matching a regular expression | +Macro | +
filterout ';' LIST ';' REGEX | +Filters out entries in a list that matching a regular expression | +Macro | +
find ';' VALUE ';' SEARCHED | +The starting position ofof SEARCHED (not a regex) in VALUE | +Macro | +
findfile ';' PATH ( ';' FILTER ) | +A filtered list of relative paths from a directory and its subdirectories | +Project | +
findlast ';' VALUE ';' SEARCHED | +The starting position of SEARCHED (not a regex) in VALUE when searching from the end | +Macro | +
findname ';' PATH ( ';' FILTER ) | +A list of filtered by name resource paths with optional replacement | +Project | +
findpath ';' REGEX ( ';' REPLACE )? | +A list of filtered by path resource paths with optional replacement | +Project | +
findproviders ';' namespace ( ';' FILTER ( ';' STRATEGY)? )? | +find resources in the workspace repository matching the given namespace and optional filter. Intended for use in bndrun files. STRATEGY can one of ALL, REPOS or WORKSPACE. | +Workspace | +
first (';' LIST )* | +First element of a list | +Macro | +
fmodified ( ';' RESOURCE )+ | +Latest modification date of a list of resources | +Macro | +
foreach ';' MACRO (';' LIST)* | +Iterator over a list, calling a macro with the value and index | +Macro | +
format ';' STRING (';' ANY )* | +Print a formatted string using Locale.ROOT, automatically converting variables to the specified format if possible. | +Macro | +
frange ';' VERSION ( ';' BOOLEAN )? | +a range expression for a filter from a version. By default this is based on consumer compatibility. You can specify a third argument (true) to get provider compatibility. | +Analyzer | +
gestalt ';' NAME ( ';' NAME (';' ANY )? )? | +provides access to the gestalt properties that describe the environment. | +Workspace | +
get ';' INDEX (';' LIST )* | +The element from the concatenated lists at the given index | +Macro | +
githead | +Get the head commit number. Look for a .git/HEAD file, going up in the file hierarchy. Then get this file, and resolve any symbolic reference. | +Builder | +
glob ';' GLOBEXP | +Return the regular expression for the specified glob expression | +Macro | +
global ';' KEY ( ';' DEFAULT )? | +A current user setting from the ~/.bnd/settings.json file | +Workspace | +
ide ';' ( 'javac.target' | 'javac.source' ) | +This reads the source and target settings from the IDE | +Project | +
if ';' STRING ';' STRING ( ';' STRING )? | +Conditional macro that depending on a condition returns either a value for true or optionally for false. | +Macro | +
imports | +A list of the currently imported package names | +Analyzer | +
indexof ';' STRING (';' LIST )* | +The index of the given string in the list, or -1 if not found | +Macro | +
is ( ';' ANY )* | +Check if the given values are all equal | +Macro | +
isdir ( ';' FILE )+ | +True if all given files are directories, false if no file arguments | +Macro | +
isempty ( ';' STRING )* | +True if all given strings are empty | +Macro | +
isfile (';' FILE )+ | +Returns true if all given files actually exist and are not a directory or special file. | +Macro | +
isnumber ( ';' STRING )* | +Check if the given strings are numbers | +Macro | +
join ( ';' LIST )+ | +Join a number of list/values into a single list | +Macro | +
js (';' JAVASCRIPT )* | +Execute Javascript, return the value of the last expression | +Macro | +
last (';' LIST )* | +Last element of a list | +Macro | +
lastindexof ';' STRING (';' LIST )* | +The last index of the given string in the list, or -1 if not found | +Macro | +
length STRING | +The length of the given string | +Macro | +
list (';' KEY)* | +Returns a list of the values of the named properties with escaped semicolons. | +Macro | +
literal ';' STRING | +A literal value for the macro, i.e. it surrounds the value with the macro prefix and suffix. | +Macro | +
long2date | +Turn a long time into a date | +Macro | +
lsa ';' DIR (';' SELECTORS ) | +A list of absolute paths for files in the given directory optionally filtered by selectors. | +Macro | +
lsr ';' DIR (';' SELECTORS ) | +A list of file names in the given directory optionally filtered by selectors. | +Macro | +
map ';' MACRO (';' LIST)* | +Map a list to a new list using a function | +Macro | +
matches STRING REGEX | +Check if the given string matches the regular expression | +Macro | +
maven_version ';' MAVEN-VERSION | +Cleanup a potential maven version to make it match an OSGi Version syntax. | +Builder | +
max (';' LIST )* | +Maximum string in the lists | +Macro | +
md5 ';' RESOURCE | +The MD5 digest of an existing resource in the JAR | +Analyzer | +
min (';' LIST )* | +Minimum string in the lists | +Macro | +
native_capability ( ';' ( 'os.name' | 'os.version' | 'os.processor' ) '=' STRING )* | +Create a Require-Capability header based on the current platform or explicit values | +Processor | +
ncompare NUMBER NUMBER | +Compare two numbers by using the Double.compare method. | +Macro | +
nmax (';' LIST )* | +Maximum number in the lists | +Macro | +
nmin (';' LIST )* | +Minimum number in the lists | +Macro | +
now ( 'long' | DATEFORMAT ) | +Current date and time, default is default Date format. The format can be specified as a long or a date format. | +Macro | +
nsort (';' LIST )+ | +Concatenate a set of lists and sort their contents nummerically | +Macro | +
osfile ';' DIR ';' NAME | +Create a path to a file in OS dependent form. | +Macro | +
p_allsourcepath | +Path to all sources | +Project | +
p_bootclasspath | +The project's boot class path | +Project | +
p_buildpath | +The project's buildpath | +Project | +
p_dependson | +Provides a list of project names this project depends on | +Project | +
p_output | +The absolute path to the project's output/target directory | +Project | +
p_sourcepath | +The path to the project's source directory. | +Project | +
p_testpath | +The path of JARs placed on the remote VM's classpath for testing | +Project | +
packageattribute ';' PACKAGE (';' ATTRIBUTE)? | +The value of a package attribute | +Analyzer | +
packages | +A list of package names filtered by a query language | +Analyzer | +
path ( ';' FILES )+ | +A list of file paths separated by the platform's path separator. | +Macro | +
pathseparator | +The platform's path separator | +Macro | +
permissions (';' ( 'packages' | 'admin' | 'permissions' ) )+ | +A file in the format for the OSGi permissions resource. | +Builder | +
propertiesdir | +The directory of the properties file | +Processor | +
propertiesname | +Return the name of the properties file | +Project | +
rand (';' MIN ' (;' MAX )?)? | +A random number between 0 and 100, or between the given range (inclusive). | +Macro | +
random | +Generate a random string, which is guaranteed to be a valid Java identifier | +Processor | +
range ';' RANGE_MASK ( ';' VERSION ) | +Create a semantic version range out of a version using a mask to control the bump of the ceiling | +Macro | +
reject ';' LIST ';' REGEX | +Rejects a list by matching it against a regular expression | +Macro | +
removeall ';' LIST ';' LIST | +Return the first list where items from the second list are removed | +Macro | +
replace ';' LIST ';' REGEX (';' STRING (';' STRING)? )? | +Replace elements in a list when it matches a regular expression | +Macro | +
replacelist ';' LIST ';' REGEX (';' STRING (';' STRING)? )? | +Replace elements in a list when it matches a regular expression | +Macro | +
replacestring ';' STRING ';' REGEX (';' STRING )? | +Replace elements in a string when it matches a regular expression | +Macro | +
repo ';' BSN ( ';' VERSION ( ';' STRATEGY )? )? | +Provides the file paths to artifact in the repositories | +Project | +
repodigests ( ';' NAME )* | +Get the repository digests (describing their contents) for all or the specified names | +Workspace | +
repos | +A list of the current repositories | +Project | +
retainall ';' LIST ';' LIST | +Return the first list where items not in the second list are removed | +Macro | +
reverse (';' LIST )* | +A reversed list | +Macro | +
select ';' LIST ';' REGEX | +Selects entries in a list that matching a regular expression | +Macro | +
separator | +The platform file separator | +Macro | +
sha1 ';' RESOURCE | +The SHA-1 digest of an existing resource in the JAR | +Analyzer | +
size ( ';' LIST )* | +Count the number of elements (of all collections combined) | +Macro | +
sjoin ';' SEPARATOR ( ';' LIST )+ | +Join a number of list/values into a single list with a given separator | +Macro | +
sort (';' LIST )+ | +Concatenate a set of lists and sort their contents on their string value | +Macro | +
split ';' REGEX (';' STRING )* | +Split a number of strings into a list using a regular expression | +Macro | +
startswith ';' STRING ';' PREFIX | +Check if the given string starts with the given prefix | +Macro | +
stem ';' STRING | +Return the string up to but not including the first dot | +Macro | +
sublist ';' START ';' END (';' LIST )* | +Return a sublist of the list | +Macro | +
subst ';' STRING ';' REGEX (';' STRING (';' NUMBER )? )? | +Substitute all the regex matches in the target for the given value; if a count is specified, limit the number of replacements to that count. | +Macro | +
substring ';' STRING ';' START ( ';' END )? | +Return a substring of a given string, negative indexes allowed | +Macro | +
sum (';' LIST )* | +The sum of a list | +Macro | +
system ';' STRING ( ';' STRING )? | +Execute a system command | +Macro | +
system_allow_fail ';' STRING ( ';' STRING )? | +Execute a system command but ignore any failures | +Macro | +
template ';' NAME [ ';' template ]+ | +Expand the entries of a merged and decorated Parameters object using a template that can refer to the key and attributes | +Macro | +
thisfile | +Return the name of the properties file for this Processor | +Processor | +
toclasspath ';' LIST ( ';' BOOLEAN )? | +Convert a list of class names to a list of paths. | +Macro | +
toclassname ';' FILES | +Translate a list of relative file paths to class names. The files can either end with .class or .java | +Macro | +
tolower STRING | +Turn a string into an lower case string | +Macro | +
toupper STRING | +Turn a string into an uppercase string | +Macro | +
trim ';' STRING | +Remove whitespace around the given string | +Macro | +
tstamp ( ';' DATEFORMAT ( ';' TIMEZONE ( ';' LONG )? )? )? | +Create a timestamp based on a date format. Default format is "yyyyMMddHHmm" | +Macro | +
unescape ( ';' STRING )* | +The concatenated input will have all \n, \r, \b, \f, and \t replaced with their control code. | +Macro | +
uniq (';' LIST )* | +Concatenate the lists and then remove any duplicates. | +Macro | +
uri ';' URI (';' URI)? | +Resolve a uri against a base uri. | +Processor | +
user ';' KEY ( ';' DEFAULT )? | +A current user setting from the ~/.bnd/settings.json file | +Workspace | +
vcompare VERSION VERSION | +Compare two version strings | +Macro | +
version MASK VERSION? | +Modify a version using a template. This is an alias to the versionmask macro. | +Macro | +
version_cleanup ';' VERSION | +Cleanup a potential maven version to make it match the OSGi Version syntax. | +Macro | +
versionmask MASK VERSION? | +Modify a version using a template | +Macro | +
vmax (';' LIST )* | +Maximum version in the lists | +Macro | +
vmin (';' LIST )* | +Minimum version in the lists | +Macro | +
warning ( ';' STRING )* | +Raise an error consisting of all concatenated strings | +Macro | +
workspace | +The absolute file path to the current workspace | +Workspace | +
Plugins are objects that can extend the functionality of bnd. They are called from inside bnd when a certain action should take place. For example, bnd uses a repository and plugins provide the actual repository implementations. Or for example, the SpringComponent analyzes the Spring files and adds references found in that XML to the imports.
+ +A plugin is defined as:
+ +PLUGIN ::= FQN ( ';' \<directive\|attribute\> )*
+
The following directive is defined for all plugin:
+ ++ | path: |
+ + | A path to the jar file that contains the plugin. The directory/jar at that location is placed on your classpath for that plugin. | ++ |
External Plugins are external code to bnd code but that can be executed from within bnd. The JARs for this code are coming from the Workspace repository. The External Plugin Namespace defines the namespace (bnd.external.plugin
) and the following attributes:
Attribute | +Description | +
---|---|
bnd.external.plugin |
+ Defines the name of the plugin, should follow simple token syntax | +
objectClass | +The interface type the implementation should implement | +
implementation | +The implementation fully qualified class name | +
subtype | +Optional subtype when the objectClass has a type parameter |
+
version | +Optional version range to limit the candidates | +
There is an annotation, aQute.bnd.service.externalplugin.ExternalPlugin
, that can be applied on a type.
For example:
+ +aQute.bnd.service.externalplugin.ExternalPlugin(
+ name = "calling",
+ objectClass = Callable.class,
+ subtype = String.class)
+public class CallImpl implements Callable<String> {
+ public String call() throws Exception {
+ return "hello";
+ }
+}
+
In Bndtools, you can declare any class as an ExternalPlugin. The automatic build features will automatically build the JAR of the plugin and this will immediately become available in the rest of the build. If you use external plugins from the local workspace, make sure to declare a -dependson
to the external plugin project in any project that uses it, this dependency is not automatically detected.
A JAR can contain any number of external plugins. It must ensure that it does not have any dependencies outside the bndlib it was compiled against.
+ +For a build model it is crucial that it has no dependencies on the machine it is running on. One of the most frustrating developer experiences is the phrase: ‘Yes, but it runs on my machine!’. For this reason bnd goes out of it is way to have no dependencies outside its workspace. That is, until we hit security. It is in the nature of security to have secrets and by definitions these secrets cannot be in the workspace, or, well, they will not be that secret for long. Though the best solution is to keep the secret in your head, there are times when this is impossible or plain cumbersome.
+ +For this reason, bnd maintains a settings file in ~/.bnd/settings.json. This file contains ordinary settings …
+ +TBD
+ +The settings automatically generate a private and public key when they are initialized. However, in certain cases it is necessary to explicitly authorize a system. For example, Travis always starts a build from a completely freshly initialized VM. In such a case it is necessary to authorize the machine explicitly. Though it is possible to just copy an existing settings file to this machine, it is more elegant to use the bnd commands. Do the following steps to authorize a machine.
+ +On a trusted host, get the current private and public key and the email:
+ +$ bnd identity
+--publicKey 08E...CF --privateKey C7FE...D3 --email bnd@example.org
+
The output is a bit longer than shown because these keys are darned long, you know, security is hard. You should copy the output and then on the target system run:
+ +$ bnd identity <copied output>
+
This will use the same authorization as the original machine. If you want to create a more unique authorization, then it is possible to create a new private/public key pair.
+ + $ bnd settings -l temp.json -g
+ $ bnd identity -l temp.json
+ --publicKey 08E...CF --privateKey C7FE...D3 --email bnd@example.org
+ ...
+ $ bnd identity <copied output>
+
First to explain the message. In a Bndtools project, the Java version is configured in two places. For the offline bnd build with Gradle/Ant, we set it directly from the javac.source
and javac.target
properties that you find in cnf/build.bnd
. But in the internal Eclipse build, the configuration of the Eclipse project contains the JRE settings. Unfortunately we cannot force Eclipse to use the same settings as the offline build, but we can detect when they are inconsistent, and so we generate the warning you have seen.
To fix the problem, first decide what version of Java you want to use. Then you need to set that in both the bnd configuration and in the Eclipse workspace.
+ +For bnd, you can set javac.source
and javac.target
for the whole workspace in cnf/build.bnd
, and you can override it if you wish in each project’s bnd.bnd
file. You can find these entries in cnf/build.bnd
from the standard templates, it will be commented out so just uncomment it.
For Eclipse you can set a workspace-wide preference by opening Window -> Preferences
and navigating to the Java -> Compiler
preference panel. This can also be overridden on a per-project basis by right-clicking the project, selecting Properties and navigating to the Java Compiler property panel. To be really safe, it’s best to set the Eclipse compiler properties explicitly per-project. This is because these properties are persisted by Eclipse into the .settings folder inside the project, which can be checked into source control, whereas the workspace-wide settings go into the workspace .metadata directory which is not usually checked in.
You can use the bnd discourse site mail list or mail me.
+ +Sometimes bnd reports imports that seem plain wrong. Believe me, they are almost always right. bnd does a thorough analysis of the byte codes in your class files and when it imports something it is almost sure to be a reference in your code. How can you find the culprit? Bndtools has tooling to drill down into your code. bnd also can print out the JAR and look at the [USEDBY]
section to find the package(s) that cause the import.
If there is no package using the imported package, then look at the following places for imports:
+ +In the likely case the import is real but unwanted, look at Unwanted Imports.
+ +This usually indicates that:
+ +If you have an unwanted import than you can remove it from the Import-Package manifest header with the ‘!’ operator:
+ +Import-Package: !com.unwanted.reference.*, *
+
A usually better way is to make the imports optional:
+ +Import-Package: com.unwanted.reference.*;resolution:=optional, *
+
Note the end at the Import-Package statement, that wildcard ‘*’ is crucial for remaining imports, see No Imports Show Up.
+ +The imports that show up in your Import-Package manifest header are controlled by the bnd file’s Import-Package instruction. In bnd, the list is a set of patterns that are sequentially applied to your imports as calculated by bnd from the classes, resources, and headers in the JAR.
+ +The default Import-Package bnd instruction is:
+ +Import-Package: *
+
This imports all referred packages. The most common reasons imports do not appear in the manifest is that the default is overridden and the overrider forgot to add the wildcard ‘*’ at the end. For example, the following is wrong:
+ +Import-Package: com.example; version=1.2
+
This will create an import for com.example but it ignores all other imports.
+ +Another reason is the exclude operator (‘!’) that will remove imports. If this is too wild, then no imports are left to insert in the manifest. So the following leads to no imports for any package starting with com.example.
+ +Import-Package: !com.example.*, *
+
Last but not least, look at the [USEDBY]
section of the JAR print out, make sure there are actually references.
Many people are surprised that bnd does not automatically calculate the Bundle-Activator. Basically, there are the following issues:
+ +That said, it is possible to automate the Bundle-Activator:
+ +Bundle-Activator: \
+ ${classes;IMPLEMENTS;org.osgi.framework.BundleActivator}
+
However, if there are multiple Bundle-Activators you will get an error.
+ +@Reference
automatically sets the bind method but how is the unbind method set? Simple, you use a method with a similar name:
bind | +unbind | +
---|---|
setX | +unsetX | +
addX | +removeX | +
For example:
+ +@Reference
+protected void setFoo(LogService l) { ... }
+protected void unsetFoo(LogService l) { ... }
+
If you want to override this, use
+ +@Reference(unbind="IRefuseToCallMyMethodUnFoo");
+protected void foo(LogService l) {}
+protected void IRefuseToCallMyMethodUnFoo(LogService l) {}
+
Unfortunately Java has no method references so it is not type safe.A non existent @UnReference
annotation is not very useful because that still requires linking it up symbolically to the associated @Reference
.
I’m just a little bit confused about the bnd approach with the file naming “packageinfo”. The JLS already defines “package-info.java” for package relevant infos. Wouldn’t it be simplier (less complex) to have only one file for package definitions? So, the bnd tool could manage the package version in “package-info.java”, too.
+ +You can use either file. The reason there are two options is that one constituency thinks annotations are the only solution and the other is running pre Java 5 … Basically if I would have to choose it would be packageinfo because that works anywhere but I expect that you probably would not like that :-)
+ +Annotations are not inherited from the component’s super classes by default. The problem is that super classes from imported packages may be different at runtime than they were at build time. So it is always best to declare your annotations on the actual component class. Alternatively you can use the instruction -dsannotations-options: inherit
. Then bnd will use DS annotations found in the class hierarchy of the component class. This will not work for the @Component
annotation itself; it will not be inherited from super classes causing a subclass to suddenly be a component.
If no explicit export version is specified in the bnd file then bnd will look in the following places.
+ +The use of the bundle version is a rather bad idea, packages should be versioned independently. It is possible +to not use the bundle version for a package with:
+ +-nodefaultversion true
+
No. It creates unnecessary complexity, it is slower, and it is not necessary. It will also not be compatible with techniques like PojoSR, something that the OSGi is looking into standardizing.
+ +Just use Private-Package and Export-Package, know what goes into your JAR. If you really need to wrap one or more JARs, use the Include-Resource instruction, it has an option to unroll a JAR resource (see @ option. This will copy all its contents in the target JAR. The -exportcontents can then be used to export selected packages. Even better is of course to know
+ +The [Best Practices section[1] provides some good tips about how you setup workspaces that share some information.
+ +If an entry is wrong, send a mail with the JAR that has the problem. Preferably as small as possible.
+ + +Create a JAR
+ +create
+ +Compare a newer bundle to a baselined bundle and provide versioning advice.
+ +[ -a, --all ] - Show all, also unchanged
+[ -d, --diff ] - Show any differences
+[ -f, --fixup <string> ] - Output file with fixup info
+[ -p, --packages] - Packages to baseline (comma delimited)
+[ -q, --quiet ] - Be quiet, only report errors
+[ -v, --verbose ] - On changed, list API changes
+
bnd baseline --diff newer.jar older.jar
Generate autocompletion file for bash
+ +@Description("Generate autocompletion file for bash")
+public void _bash(Options options) throws Exception {
+ File tmp = File.createTempFile("bnd-completion", ".tmp");
+ tmp.deleteOnExit();
+
+ try {
+ IO.copy(getClass().getResource("bnd-completion.bash"), tmp);
+
+ Sed sed = new Sed(tmp);
+ sed.setBackup(false);
+
+ Reporter r = new ReporterAdapter();
+ CommandLine c = new CommandLine(r);
+ Map<String,Method> commands = c.getCommands(this);
+ StringBuilder sb = new StringBuilder();
+ for (String commandName : commands.keySet()) {
+ sb.append(" " + commandName);
+ }
+ sb.append(" help");
+
+ sed.replace("%listCommands%", sb.toString().substring(1));
+ sed.doIt();
+ IO.copy(tmp, out);
+ }
+ finally {
+ tmp.delete();
+ }
+}
+
The swiss army tool for OSGi
+ +bnd
+ +Interactive gogo shell
+ +/**
+ * start a local framework
+ */
+
+interface BootstrapOptions extends Options {
+
+}
+
+public void _bootstrap(BootstrapOptions options) throws Exception {
+ Workspace ws = getWorkspace(getBase());
+ File buildDir = ws.getBuildDir();
+ File bndFile = IO.getFile(buildDir, "bnd.bnd");
+ if ( !bndFile.isFile()) {
+ error("No bnd.bnd file found in cnf directory %s", bndFile);
+ return;
+ }
+
+ Run run = new Run(ws, buildDir, bndFile);
+
+ run.runLocal();
+
+ getInfo(run);
+}
+
From a set of bsns, create a list of urls if found in the repo
+ +/**
+ * From a set of bsns, create a list of urls
+ */
+
+interface Bsn2UrlOptions extends projectOptions {
+
+}
+
+static Pattern LINE_P = Pattern.compile("\\s*(([^\\s]#|[^#])+)(\\s*#.*)?");
+
+public void _bsn2url(Bsn2UrlOptions opts) throws Exception {
+ Project p = getProject(opts.project());
+
+ if (p == null) {
+ error("You need to be in a project or specify the project with -p/--project");
+ return;
+ }
+
+ MultiMap<String,Version> revisions = new MultiMap<String,Version>();
+
+ for (RepositoryPlugin repo : p.getPlugins(RepositoryPlugin.class)) {
+ if (!(repo instanceof InfoRepository))
+ continue;
+
+ for (String bsn : repo.list(null)) {
+ revisions.addAll(bsn, repo.versions(bsn));
+ }
+ }
+
+ for (List<Version> versions : revisions.values()) {
+ Collections.sort(versions, Collections.reverseOrder());
+ }
+
+ List<String> files = opts._();
+
+ for (String f : files) {
+ BufferedReader r = IO.reader(getFile(f));
+ try {
+ String line;
+ nextLine: while ((line = r.readLine()) != null) {
+ Matcher matcher = LINE_P.matcher(line);
+ if (!matcher.matches())
+ continue nextLine;
+
+ line = matcher.group(1);
+
+ Parameters bundles = new Parameters(line);
+ for (Map.Entry<String,Attrs> entry : bundles.entrySet()) {
+
+ String bsn = entry.getKey();
+ VersionRange range = new VersionRange(entry.getValue().getVersion());
+
+ List<Version> versions = revisions.get(bsn);
+ if (versions == null) {
+ error("No for versions for " + bsn);
+ break nextLine;
+ }
+
+ for (Version version : versions) {
+ if (range.includes(version)) {
+
+ for (RepositoryPlugin repo : p.getPlugins(RepositoryPlugin.class)) {
+
+ if (!(repo instanceof InfoRepository))
+ continue;
+
+ InfoRepository rp = (InfoRepository) repo;
+ ResourceDescriptor descriptor = rp.getDescriptor(bsn, version);
+ if (descriptor == null) {
+ error("Found bundle, but no descriptor %s;version=%s", bsn, version);
+ return;
+ }
+
+ out.println(descriptor.url + " #" + descriptor.bsn + ";version="
+ + descriptor.version);
+ }
+ }
+ }
+
+ }
+
+ }
+ }
+ catch (Exception e) {
+ error("failed to create url list from file %s : %s", f, e);
+ }
+ finally {
+ r.close();
+ }
+ }
+}
+
Build a project. This will create the jars defined in the bnd.bnd and sub-builders.
+ +[ -f, --full ] - Do full
+[ -p, --project <string> ] - Identify another project
+[ -t, --test ] - Build for test
+
Build project, is deprecated but here for backward compatibility. If you use it, you should know how to use it so no more info is provided.
+ +buildx [options] ...
+
[ -c, --classpath <string>* ] - A list of JAR files and/or directories that should be placed on the class path before
+ the calculation starts.
+[ -e, --eclipse ] - Parse the file as an Eclipse .classpath file, use the information to create an Eclipse's
+ project class path. If this option is used, the default .classpath file is not read.
+[ -f, --force ] -
+[ -n, --noeclipse ] - Do not parse the .classpath file of an Eclipse project.
+[ -o, --output <string> ] - Override the default output name of the bundle or the directory. If the output is a
+ directory, the name will be derived from the bnd file name.
+[ -p, --pom ] -
+[ -s, --sourcepath <string>* ]-
+[ -S, --sources ] -
+
bnd buildx -classpath bin -noeclipse -output test.jar xyz.bnd
Bumps the version of a project. Will take the current version and then increment with a major, minor, or micro increment. The default bump is minor.
+ +[ -p, --project <string> ] - Path to another project than the current project
+
Show the changes in this release of bnd
+ +changes [options]
+
[ -a, --all ] - Print all releases
+
Clean a project
+ +[ -p, --project <string> ] - Path to another project than the current project
+
Converter to different formats
+ +[ -m, --m2p ] - Convert a manifest to a properties files
+[ -x, --xml ] - Save as xml
+
Equivalent jar command c[v0mf] command (supports the jar tool’s syntax). Will wrap the bundle unless –wrapnot is specified
+ +create [options] ...
+
[ -b, --bsn <string> ] - Bundle Symbolic Name for wrap
+[ -c, --cdir <string> ] - directory
+[ -f, --file <string> ] - Jar file (f option)
+[ -F, --force ] - Force write event if there are errors
+[ -m, --manifest ] - No manifest (M option)
+[ -n, --nocompression ] - No compression (0 option)
+[ -p, --properties <string> ] - Properties for wrapping
+[ -v, --verbose ] - Verbose (v option)
+[ -V, --version <version> ] - Bundle Version for wrap
+[ -w, --wrap ] - Wrap
+
Show a lot of info about the project you’re in
+ +[ -f, --flattened ] - Show the flattened properties
+[ -p, --project <string> ] - Path to a project, default is current directory
+
defaults
+ +Show all deliverables from this workspace. with their current version and path.
+ +[ -l, --limit ] - Only provide deliverables of this project
+[ -p, --project <string> ] - Path to project, default current directory
+
biz.aQute.bnd (master)$ bnd deliverables
+found password
+aQute.libg 2.9.0 /Ws/bnd/aQute.libg/generated/aQute.libg.jar
+biz.aQute.bnd 2.4.0 /Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar
+biz.aQute.bnd.annotation 2.4.0 /Ws/bnd/biz.aQute.bnd.annotation/generated/biz.aQute.bnd.annotation.jar
+biz.aQute.bnd.bootstrap.console 0.0.0 /Ws/bnd/biz.aQute.bnd.bootstrap/generated/biz.aQute.bnd.bootstrap.console.jar
+biz.aQute.bnd.test 2.4.0 /Ws/bnd/biz.aQute.bnd.test/generated/biz.aQute.bnd.test.jar
+biz.aQute.bnd.testextension 2.4.0 /Ws/bnd/biz.aQute.bnd.testextension/generated/biz.aQute.bnd.testextension.jar
+biz.aQute.bndlib 2.4.0 /Ws/bnd/biz.aQute.bndlib/generated/biz.aQute.bndlib.jar
+biz.aQute.bndlib.tests 2.4.0 /Ws/bnd/biz.aQute.bndlib.tests/generated/biz.aQute.bndlib.tests.jar
+biz.aQute.junit 1.3.0 /Ws/bnd/biz.aQute.junit/generated/biz.aQute.junit.jar
+biz.aQute.launcher 1.4.0 /Ws/bnd/biz.aQute.launcher/generated/biz.aQute.launcher.jar
+biz.aQute.repository 2.2.0 /Ws/bnd/biz.aQute.repository/generated/biz.aQute.repository.jar
+biz.aQute.resolve 0.2.0 /Ws/bnd/biz.aQute.resolve/generated/biz.aQute.resolve.jar
+cnf 0.0.0 /Ws/bnd/cnf/generated/cnf.jar
+demo 1.1.0 /Ws/bnd/demo/generated/demo.jar
+dist 0.0.0 /Ws/bnd/dist/generated/dist.jar
+osgi.r5 1.0.1 /Ws/bnd/osgi.r5/generated/osgi.r5.jar
+biz.aQute.bnd (master)$ bnd deliverables -l
+found password
+biz.aQute.bnd 2.4.0 /Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar
+biz.aQute.bnd (master)$
+
Compares two jars. Without specifying the JARs (and when there is a current project) the jars of this project are diffed against their baseline in the baseline repository, using the sub-builder’s options (these can be overridden). If one JAR is given, the tree is shown. Otherwise 2 JARs must be specified and they are then compared to each other.
+ +[ -a, --api ] - Print the API
+[ -f, --full ] - Show full tree
+[ -i, --ignore <string>* ] - Ignore headers
+[ -m, --manifest ] - Print the Manifest
+[ -o, --output <file> ] - Where to send the output
+[ -p, --pack <string>* ] - Limit to these packages (can have wildcards)
+[ -r, --resources ] - Print the Resources
+[ -x, --xml ] - Print difference as valid XML
+
biz.aQute.bnd (master)$ bnd diff generated/
+generated/.index generated/biz.aQute.bnd.jar generated/buildfiles
+biz.aQute.bnd (master)$ bnd diff generated/biz.aQute.bnd.jar /Ws/archive/aQute.archive/aQute.bnd/archive/tmp/biz.aQute.bnd.jar
+ADDED PACKAGE aQute.bnd.build
+ADDED PACKAGE aQute.bnd.header
+ADDED PACKAGE aQute.bnd.maven.support
+ADDED PACKAGE aQute.bnd.osgi
+ADDED PACKAGE aQute.bnd.osgi.resource
+ADDED PACKAGE aQute.bnd.service
+ADDED PACKAGE aQute.bnd.service.action
+ADDED PACKAGE aQute.bnd.service.classparser
+ADDED PACKAGE aQute.bnd.service.diff
+ADDED PACKAGE aQute.bnd.service.extension
+ADDED PACKAGE aQute.bnd.service.progress
+ADDED PACKAGE aQute.bnd.service.repository
+ADDED PACKAGE aQute.bnd.service.resolve.hook
+ADDED PACKAGE aQute.bnd.service.url
+ADDED PACKAGE aQute.bnd.util.dto
+ADDED PACKAGE aQute.bnd.version
+REMOVED PACKAGE aQute.lib.osgi
+ADDED PACKAGE aQute.service.reporter
+ADDED PACKAGE org.osgi.resource
+ADDED PACKAGE org.osgi.service.bindex
+ADDED PACKAGE org.osgi.service.repository
+ADDED CLASS_VERSION J2SE6
+CHANGED MANIFEST <manifest>
+REMOVED HEADER Bundle-ActivationPolicy:lazy
+REMOVED HEADER Bundle-Activator:aQute.bnd.plugin.Activator
+REMOVED HEADER Bundle-Copyright:2006, 2007 (c) aQute, All rights reserved
+ADDED HEADER Bundle-Copyright:Copyright (c) aQute (2000, 2014). All Rights Reserved.
+REMOVED HEADER Bundle-Description:A utility and plugin to wrap, build, or print bundles
+ADDED HEADER Bundle-Description:This command line utility is the Swiss army knife of OSGi. It provides you with a breadth
+CHANGED HEADER Bundle-License
+ REMOVED CLAUSE ASL20
+ ADDED CLAUSE http://www.opensource.org/licenses/apache2.0.php
+REMOVED HEADER Bundle-Name:aQute Bundle Tool
+ADDED HEADER Bundle-Name:biz.aQute.bnd
+ADDED HEADER Bundle-SCM:git://github.com/bndtools/bnd.git
+CHANGED HEADER Bundle-SymbolicName
+ CHANGED CLAUSE biz.aQute.bnd
+ REMOVED PARAMETER singleton::true
+REMOVED HEADER Bundle-Version:0.0.279
+ADDED HEADER Bundle-Version:2.4.0
+REMOVED HEADER Conditional-Package:aQute.libg.*, aQute.lib.*
+ADDED HEADER Conditional-Package:aQute.libg.*,aQute.lib.*,aQute.configurable
+CHANGED HEADER Export-Package
+ ADDED CLAUSE aQute.bnd.build
+ ADDED CLAUSE aQute.bnd.header
+ ADDED CLAUSE aQute.bnd.maven.support
+ ADDED CLAUSE aQute.bnd.osgi
+ ADDED CLAUSE aQute.bnd.osgi.resource
+ ADDED CLAUSE aQute.bnd.service
+ ADDED CLAUSE aQute.bnd.service.action
+ ADDED CLAUSE aQute.bnd.service.classparser
+ ADDED CLAUSE aQute.bnd.service.diff
+ ADDED CLAUSE aQute.bnd.service.extension
+ ADDED CLAUSE aQute.bnd.service.progress
+ ADDED CLAUSE aQute.bnd.service.repository
+ ADDED CLAUSE aQute.bnd.service.resolve.hook
+ ADDED CLAUSE aQute.bnd.service.url
+ ADDED CLAUSE aQute.bnd.util.dto
+ ADDED CLAUSE aQute.bnd.version
+ REMOVED CLAUSE aQute.lib.osgi
+ ADDED CLAUSE aQute.service.reporter
+ ADDED CLAUSE org.osgi.resource
+ ADDED CLAUSE org.osgi.service.bindex
+ ADDED CLAUSE org.osgi.service.repository
+ADDED HEADER Git-Descriptor:2.4.0.M1-66-gc1ad07d-dirty
+ADDED HEADER Git-SHA:c1ad07dfeb4704ce590bd93c1405d7bfe8bef131
+CHANGED HEADER Import-Package
+ ADDED CLAUSE aQute.bnd.service
+ ADDED CLAUSE aQute.bnd.service.action
+ ADDED CLAUSE aQute.bnd.service.diff
+ ADDED CLAUSE aQute.bnd.service.progress
+ ADDED CLAUSE aQute.bnd.service.repository
+ ADDED CLAUSE aQute.bnd.service.url
+ ADDED CLAUSE aQute.bnd.version
+ REMOVED CLAUSE aQute.lib.filter
+ REMOVED CLAUSE aQute.lib.osgi
+ REMOVED CLAUSE aQute.libg.generics
+ REMOVED CLAUSE aQute.libg.header
+ REMOVED CLAUSE aQute.libg.qtokens
+ REMOVED CLAUSE aQute.libg.reporter
+ REMOVED CLAUSE aQute.libg.version
+ ADDED CLAUSE aQute.service.reporter
+ ADDED CLAUSE javax.crypto
+ ADDED CLAUSE javax.crypto.spec
+ ADDED CLAUSE javax.naming
+ ADDED CLAUSE javax.net.ssl
+ ADDED CLAUSE javax.script
+ ADDED CLAUSE javax.xml.namespace
+ ADDED CLAUSE javax.xml.transform
+ ADDED CLAUSE javax.xml.transform.dom
+ ADDED CLAUSE javax.xml.transform.stream
+ ADDED CLAUSE javax.xml.xpath
+ ADDED CLAUSE junit.framework
+ CHANGED CLAUSE org.apache.tools.ant
+ ADDED PARAMETER resolution::optional
+ ADDED CLAUSE org.apache.tools.ant.taskdefs
+ CHANGED CLAUSE org.apache.tools.ant.types
+ ADDED PARAMETER resolution::optional
+ REMOVED CLAUSE org.eclipse.core.resources
+ REMOVED CLAUSE org.eclipse.core.runtime
+ REMOVED CLAUSE org.eclipse.debug.core
+ REMOVED CLAUSE org.eclipse.debug.ui
+ REMOVED CLAUSE org.eclipse.debug.ui.sourcelookup
+ REMOVED CLAUSE org.eclipse.jdt.core
+ REMOVED CLAUSE org.eclipse.jdt.debug.ui.launchConfigurations
+ REMOVED CLAUSE org.eclipse.jdt.internal.junit.launcher
+ REMOVED CLAUSE org.eclipse.jdt.junit.launcher
+ REMOVED CLAUSE org.eclipse.jdt.launching
+ REMOVED CLAUSE org.eclipse.jdt.ui.wizards
+ REMOVED CLAUSE org.eclipse.jface.action
+ REMOVED CLAUSE org.eclipse.jface.dialogs
+ REMOVED CLAUSE org.eclipse.jface.resource
+ REMOVED CLAUSE org.eclipse.jface.text
+ REMOVED CLAUSE org.eclipse.jface.text.source
+ REMOVED CLAUSE org.eclipse.jface.viewers
+ REMOVED CLAUSE org.eclipse.jface.wizard
+ REMOVED CLAUSE org.eclipse.swt.events
+ REMOVED CLAUSE org.eclipse.swt.graphics
+ REMOVED CLAUSE org.eclipse.swt.layout
+ REMOVED CLAUSE org.eclipse.swt.widgets
+ REMOVED CLAUSE org.eclipse.ui
+ REMOVED CLAUSE org.eclipse.ui.part
+ REMOVED CLAUSE org.eclipse.ui.plugin
+ REMOVED CLAUSE org.eclipse.ui.texteditor
+ CHANGED CLAUSE org.osgi.framework
+ ADDED PARAMETER version:[1.6,2)
+ ADDED CLAUSE org.osgi.resource
+ ADDED CLAUSE org.osgi.service.log
+ REMOVED CLAUSE org.osgi.service.packageadmin
+ ADDED CLAUSE org.xml.sax.helpers
+REMOVED HEADER Include-Resource:plugin.xml, LICENSE, maven-dependencies.txt
+ADDED HEADER Private-Package:aQute.bnd.annotation;version="1.43.2",aQute.bnd.annotation.component;version="1.43.1",aQute.bnd.annotation.headers;version="1.0",aQute.bnd.annotation.licenses;version="1.0",aQute.bnd.annotation.metatype;version="1.44.1",aQute.bnd.ant,aQute.bnd.build.model;version="2.6.0",aQute.bnd.build.model.clauses;version=2,aQute.bnd.build.model.conversions,aQute.bnd.compatibility,aQute.bnd.component,aQute.bnd.component.error;version="1.0.0",aQute.bnd.differ;version="1.1.0",aQute.bnd.enroute.commands,aQute.bnd.filerepo;version="1.0",aQute.bnd.gradle,aQute.bnd.help;version="1.1",aQute.bnd.indexer,aQute.bnd.indexer.analyzers,aQute.bnd.main;version="0.9",aQute.bnd.make,aQute.bnd.make.calltree,aQute.bnd.make.component,aQute.bnd.make.coverage,aQute.bnd.make.metatype,aQute.bnd.maven,aQute.bnd.obr,aQute.bnd.osgi.eclipse,aQute.bnd.properties;version="2.0",aQute.bnd.resource.repository,aQute.bnd.signing,aQute.bnd.testing;version="1.0",aQute.bnd.url;version="1.0",aQute.configurable;version="1.0.0",aQute.lib.deployer,embedded-repo.jar,org.osgi.service.component.annotations;version="1.3",org.osgi.service.coordinator;version="1.0",templates,aQute.lib.base64;version="1.2.0",aQute.lib.collections;version="1.2.0",aQute.lib.converter;version="2.0.1",aQute.lib.filter;version="1.1.0",aQute.lib.getopt;version="1.0.0",aQute.lib.hex;version="1.1.0",aQute.lib.io;version="1.4.0",aQute.lib.json;version="3.0.0",aQute.lib.justif;version="1.1.0",aQute.lib.persistentmap;version="1.1.0",aQute.lib.settings;version="1.2.0",aQute.lib.strings;version="1.1.0",aQute.lib.tag;version="1.1",aQute.libg.classdump;version="1.0",aQute.libg.command;version="3.0.0",aQute.libg.cryptography;version="1.1.0",aQute.libg.filelock;version="1.0.0",aQute.libg.filters;version="1.0",aQute.libg.forker;version="1.0",aQute.libg.generics;version="1.0",aQute.libg.glob;version="1.1.1",aQute.libg.map;version="1.2.0",aQute.libg.qtokens;version="1.0",aQute.libg.reporter;version="1.5",aQute.libg.sed;version="1.1.0",aQute.libg.tuple;version="1.0",aQute.lib.markdown
+REMOVED HEADER Private-Package:aQute.lib.deployer,aQute.bnd.test,aQute.bnd.junit,aQute.bnd.launch,aQute.lib.jardiff,aQute.bnd.build,aQute.bnd.plugin.popup.actions,aQute.bnd.annotation,aQute.bnd.service,aQute.lib.osgi.eclipse,aQute.bnd.ant,aQute.bnd.main;version="0.9",aQute.bnd.plugin,aQute.bnd.make,aQute.bnd.plugin.builder,aQute.bnd.jareditor,aQute.bnd.classpath
+ADDED HEADER Require-Capability:osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.6))"
+MAJOR RESOURCES <resources>
+MAJOR RESOURCE LICENSE
+ ADDED SHA 2B8B815229AA8A61E483FB4BA0588B8B6C491890
+ REMOVED SHA 47B573E3824CD5E02A1A3AE99E2735B49E0256E4
+REMOVED RESOURCE OSGI-OPT/bnd.bnd
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/ConsumerType.java
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/annotation/Export.java
+ ADDED SHA 57B5F624DE03F979D4876F63422BA92203EF1910
+ REMOVED SHA 6A6378BCE886CA435A3F166F70F0E0AB12828B13
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/Import.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/Pair.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/ProviderType.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/Version.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/component/Activate.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/component/Component.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/component/ConfigurationPolicy.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/component/Deactivate.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/component/Modified.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/component/Reference.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/component/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/headers/About.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/headers/BundleCategory.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/headers/BundleContributors.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/headers/BundleCopyright.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/headers/BundleDevelopers.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/headers/BundleDocURL.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/headers/BundleLicense.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/headers/Category.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/headers/ProvideCapability.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/headers/RequireCapability.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/headers/Resolution.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/headers/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/licenses/ASL_2_0.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/licenses/BSD_2_Clause.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/licenses/BSD_3_Clause.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/licenses/CDDL_1_0.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/licenses/CPL_1_0.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/licenses/EPL_1_0.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/licenses/GPL_2_0.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/licenses/GPL_3_0.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/licenses/LGPL_2_1.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/licenses/MIT_1_0.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/licenses/MPL_2_0.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/licenses/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/metatype/Configurable.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/metatype/Meta.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/metatype/packageinfo
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/package-info.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/annotation/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/ant/AntMessages.java
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/ant/BaseTask.java
+ REMOVED SHA 45CF7C2907DD82AD23A979E880C20007206AA0E6
+ ADDED SHA C27D9C96DFCDB750A1FDBA926EE99A1ED5B154B2
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/ant/BndTask.java
+ ADDED SHA 881EC2597543A91A9F1A883817C55D2B80F12CB7
+ REMOVED SHA ABCC53977C4924B29EDF334DA1B3FE5010EFD3B6
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/ant/ConsoleProgress.java
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/ant/DeployTask.java
+ ADDED SHA 33ED2BB9356BCFBD7A0D0AF8EB1BB10B6D7E7C7D
+ REMOVED SHA CF24BA22F1A33E61F950E34317F0D82082B08921
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/ant/EclipseTask.java
+ REMOVED SHA 62E6497D3868BD6E48C86689E6CE26157A33AC6D
+ ADDED SHA B6D59DF94689FEEE1166D9EAC0938CCF92812115
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/ant/ExpandPropertiesTask.java
+ REMOVED SHA 08B914BB997480B60C3F48E8A5CB4BD1E7E84EF9
+ ADDED SHA B429909161768A44F2BE870C43E3F3DA84E6D827
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/ant/PackageTask.java
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/ant/PrepareTask.java
+ REMOVED SHA 60468618217981C8ED498CE4D66668C7D3A0459B
+ ADDED SHA 949B04B29AEBC412A3485BF9AFAF446ED292DBF8
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/ant/ProjectBuildOrderTask.java
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/ant/ProjectTask.java
+ ADDED SHA 9540543903DD936FFAD6C972D40C772C3B6CD3B0
+ REMOVED SHA 9D9BF171CB0AF435A92FA6F7F7BA6D6ADDAE8A22
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/ant/ReleaseTask.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/ant/RunconfigToDistributionTask.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/ant/TestTask.java
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/ant/WrapTask.java
+ ADDED SHA 164E838D471D1773C0010560A3B419EE5D666418
+ REMOVED SHA 62318CD6C8F4D42EFDA0C39D22F418185091A712
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/build/CircularDependencyException.java
+ ADDED SHA 2DBD8F2F5382CBF94E4F495B085C44770FD52796
+ REMOVED SHA 903622D5709FBB2D44120AC242F8A6BAB2ED9604
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/build/Container.java
+ REMOVED SHA 914441F7D5DBB2F86BFD83F4FAC9EB047E464ABD
+ ADDED SHA A897B7CAFD1004D6FA0F06625F25CAB5D19CBFA6
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/DownloadBlocker.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/ErrorDetails.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/build/Framework.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/JUnitLauncher.java
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/build/Project.java
+ ADDED SHA C7A0428FE040DA06D28D865209F573B88D0ABCB5
+ REMOVED SHA D33BBA8757E45959B32ED2283B1E176DFF8A6BAE
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/build/ProjectBuilder.java
+ REMOVED SHA 4BC12A6E5EE452B093F8EDCFC7810D7E475C89B3
+ ADDED SHA 6069CBE1015591C5E02C3A63272EE499573A12A4
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/ProjectLauncher.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/ProjectMessages.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/ProjectTester.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/ReflectAction.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/ResolverMode.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/Run.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/ScriptAction.java
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/build/Workspace.java
+ REMOVED SHA 07AF5474B58E11D2806FBD6AAC0DBFB4B8F66EBC
+ ADDED SHA 6A9DEF93C7F8704B74059D56AFD4A7F279FB3D75
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/WorkspaceRepository.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/BndEditModel.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/EE.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/clauses/ComponentSvcReference.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/clauses/ExportedPackage.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/clauses/HeaderClause.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/clauses/ImportPattern.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/clauses/ServiceComponent.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/clauses/VersionedClause.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/clauses/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/ClauseListConverter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/CollectionFormatter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/Converter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/DefaultBooleanFormatter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/DefaultFormatter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/EEConverter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/EEFormatter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/EnumConverter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/EnumFormatter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/HeaderClauseConverter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/HeaderClauseFormatter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/HeaderClauseListConverter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/MapFormatter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/NewlineEscapedStringFormatter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/NoopConverter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/PropertiesConverter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/PropertiesEntryFormatter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/RequirementFormatter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/RequirementListConverter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/SimpleListConverter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/StringEntryConverter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/conversions/VersionedClauseConverter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/model/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/build/packageinfo
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/classpath/BndContainer.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/classpath/BndContainerInitializer.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/classpath/BndContainerPage.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/classpath/ModelListener.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/compatibility/Access.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/compatibility/GenericParameter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/compatibility/GenericType.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/compatibility/Kind.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/compatibility/ParseSignatureBuilder.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/compatibility/RuntimeSignatureBuilder.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/compatibility/Scope.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/compatibility/Signatures.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/component/AnnotationReader.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/component/ComponentDef.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/component/DSAnnotations.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/component/HeaderReader.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/component/ReferenceDef.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/component/TagResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/component/error/DeclarativeServicesAnnotationError.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/component/error/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/differ/Baseline.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/differ/DiffImpl.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/differ/DiffPluginImpl.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/differ/Element.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/differ/JavaElement.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/differ/RepositoryElement.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/differ/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/enroute/commands/EnrouteCommand.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/enroute/commands/EnrouteOptions.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/filerepo/FileRepo.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/filerepo/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/header/Attrs.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/header/OSGiHeader.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/header/Parameters.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/header/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/help/Syntax.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/help/Warnings.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/help/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/indexer/MimeType.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/indexer/Namespaces.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/indexer/RepoIndex.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/indexer/ResourceAnalyzer.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/indexer/analyzers/BlueprintAnalyzer.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/indexer/analyzers/BundleAnalyzer.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/indexer/analyzers/EE.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/indexer/analyzers/KnownBundleAnalyzer.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/indexer/analyzers/OSGiFrameworkAnalyzer.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/indexer/analyzers/SCRAnalyzer.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/indexer/analyzers/Yield.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/jareditor/JarConfiguration.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/jareditor/JarDocumentProvider.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/jareditor/JarEditor.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/junit/OSGiArgumentsTab.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/junit/OSGiJUnitLaunchShortcut.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/junit/OSGiJUnitLauncherConfigurationDelegate.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/junit/OSGiJUnitTabGroup.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/launch/LaunchDelegate.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/launch/LaunchTabGroup.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/launch/Shortcut.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/main/BaselineCommands.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/main/BndMessages.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/main/DiffCommand.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/main/PatchCommand.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/main/RepoCommand.java
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/main/bnd.java
+ ADDED SHA 57EE3BDF196034FB708705A3F4656111277E9F97
+ REMOVED SHA 6CCDD322FC98BF0D7ACA4F26451A62F02D7A078A
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/main/packageinfo
+ REMOVED SHA 4CBF1AE09B541F925C0D6BCBB8684EF85A0B8373
+ ADDED SHA D6E9A3759405E18A266202C40D9AA4EBA6F83A87
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/make/Make.java
+ ADDED SHA 32419B285AD910CED3A1BE2CA2DE06103634D997
+ REMOVED SHA ABD503ABDB9F0A9D20FCDDB541B819EC0D70BA70
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/make/MakeBnd.java
+ ADDED SHA 7A16BD28EBD4A2334424FB9836644D4ABCBFFABC
+ REMOVED SHA C4FC212EB0584C41BA276F679A0F137802F387AF
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/make/MakeCopy.java
+ REMOVED SHA 06D298002296B2AEAFA4412515174712A2893ED2
+ ADDED SHA D4C879A84C0D44461049118CFD2B6DE835EC0295
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/make/calltree/CalltreeResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/make/component/ComponentAnnotationReader.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/make/component/ServiceComponent.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/make/coverage/Coverage.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/make/coverage/CoverageResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/make/metatype/MetaTypeReader.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/make/metatype/MetatypePlugin.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/BsnToMavenPath.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/MavenCommand.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/MavenDependencyGraph.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/MavenDeploy.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/MavenDeployCmd.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/MavenGroup.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/MavenRepository.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/PomFromManifest.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/PomParser.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/PomResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/support/CachedPom.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/support/Maven.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/support/MavenEntry.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/support/MavenRemoteRepository.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/support/Pom.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/support/ProjectPom.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/support/Repo.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/maven/support/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/obr/OBRFragment.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/About.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/AbstractResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Analyzer.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/AnalyzerMessages.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Annotation.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/AnnotationHeaders.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Builder.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/BundleId.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/ClassDataCollector.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/ClassDataCollectors.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Classpath.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Clazz.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/CombinedResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/CommandResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Constants.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Contracts.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Descriptors.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Domain.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/EmbeddedResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/FileResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Instruction.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Instructions.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Jar.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/JarResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Macro.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/OSInformation.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/OpCodes.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Packages.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/PreprocessResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Processor.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Resource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/URLResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/Verifier.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/WriteResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/ZipResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/eclipse/EclipseClasspath.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/resource/CapReq.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/resource/CapReqBuilder.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/resource/CapabilityImpl.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/resource/FilterParser.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/resource/Filters.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/resource/PersistentResource.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/resource/RequirementImpl.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/resource/ResourceBuilder.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/resource/ResourceImpl.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/osgi/resource/packageinfo
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/plugin/Activator.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/plugin/Central.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/plugin/builder/BndBuilder.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/plugin/builder/BndNature.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/plugin/builder/ToggleNatureAction.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/plugin/popup/actions/InstallBundle.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/plugin/popup/actions/MakeBundle.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/plugin/popup/actions/VerifyBundle.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/plugin/popup/actions/WrapBundle.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/BadLocationException.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/CopyOnWriteTextStore.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/Document.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/GapTextStore.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/IDocument.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/IRegion.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/ITextStore.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/Line.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/LineTracker.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/LineType.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/PropertiesLineReader.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/Region.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/properties/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/resource/repository/ResourceDescriptorImpl.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/resource/repository/ResourceRepositoryImpl.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/Actionable.java
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/service/AnalyzerPlugin.java
+ REMOVED SHA 01DCF34F1B2C6961BC001D6EB5D68BFFEEF15F70
+ ADDED SHA D25A7039580D56816F0E9ECE913C23644B43802A
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/BndListener.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/CommandPlugin.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/Compiler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/DependencyContributor.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/Deploy.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/EclipseJUnitTester.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/IndexProvider.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/LauncherPlugin.java
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/service/MakePlugin.java
+ ADDED SHA 1DD964999442F1CEAED362243DA916643C348359
+ REMOVED SHA C6C083B804A2784D789182E00597B32CDFB89A78
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/Plugin.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/Refreshable.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/Registry.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/RegistryDonePlugin.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/RegistryPlugin.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/RemoteRepositoryPlugin.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/RepositoryListenerPlugin.java
+MAJOR RESOURCE OSGI-OPT/src/aQute/bnd/service/RepositoryPlugin.java
+ REMOVED SHA 3A6380D8D841D53668F18EF37363EFFA25A9FC73
+ ADDED SHA BC4B6801701A62CE8D4FD31090D31357D7595DC3
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/ResolutionPhase.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/ResourceHandle.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/Scripter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/SignerPlugin.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/Strategy.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/action/Action.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/action/NamedAction.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/action/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/classparser/ClassParser.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/classparser/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/diff/Delta.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/diff/Diff.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/diff/Differ.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/diff/Tree.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/diff/Type.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/diff/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/extension/ExtensionActivator.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/extension/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/progress/ProgressPlugin.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/progress/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/repository/InfoRepository.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/repository/MinimalRepository.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/repository/Phase.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/repository/RepositoryDigest.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/repository/ResourceRepository.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/repository/SearchableRepository.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/repository/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/resolve/hook/ResolverHook.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/resolve/hook/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/url/TaggedData.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/url/URLConnectionHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/url/URLConnector.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/service/url/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/signing/JartoolSigner.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/signing/Signer.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/bnd/test/ProjectLauncher.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/testing/DSTestWiring.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/testing/TestingLog.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/testing/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/url/BasicAuthentication.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/url/BndAuthentication.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/url/ConnectionSettings.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/url/DefaultURLConnectionHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/url/HttpsVerification.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/url/MultiURLConnectionHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/url/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/util/dto/DTO.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/util/dto/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/version/Version.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/version/VersionRange.java
+ADDED RESOURCE OSGI-OPT/src/aQute/bnd/version/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/configurable/Config.java
+ADDED RESOURCE OSGI-OPT/src/aQute/configurable/Configurable.java
+ADDED RESOURCE OSGI-OPT/src/aQute/configurable/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/base64/Base64.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/base64/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/collections/EnumerationIterator.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/collections/ExtList.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/collections/IteratorList.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/collections/LineCollection.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/collections/Logic.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/collections/MultiMap.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/collections/SortedList.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/collections/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/converter/Converter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/converter/TypeReference.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/converter/packageinfo
+MAJOR RESOURCE OSGI-OPT/src/aQute/lib/deployer/FileRepo.java
+ REMOVED SHA 43016599CDFB8D16479E06DBADA03D8AF32783F1
+ ADDED SHA C447FBEB09414CF0ECF5508BA06A649DC9DD34FD
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/deployer/ProjectRepo.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/deployer/RDImpl.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/deployer/RepoDeployer.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/filter/Filter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/filter/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/getopt/Arguments.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/getopt/CommandLine.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/getopt/CommandLineMessages.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/getopt/Description.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/getopt/OptionArgument.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/getopt/Options.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/getopt/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/hex/Hex.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/hex/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/io/IO.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/io/IOConstants.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/io/LimitedInputStream.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/io/packageinfo
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/jardiff/Diff.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/ArrayHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/BooleanHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/ByteArrayHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/CharacterHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/CollectionHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/DateHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/Decoder.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/Encoder.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/EnumHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/FileHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/Handler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/JSONCodec.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/MapHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/NumberHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/ObjectHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/SpecialHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/StringHandler.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/json/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/justif/Justif.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/justif/Table.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/justif/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/markdown/MarkdownFormatter.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/About.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/Analyzer.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/Builder.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/Clazz.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/Constants.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/EmbeddedResource.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/FileResource.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/Instruction.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/Jar.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/JarResource.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/Macro.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/OpCodes.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/Plugin.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/PreprocessResource.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/Processor.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/Resource.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/URLResource.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/Verifier.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/ZipResource.java
+REMOVED RESOURCE OSGI-OPT/src/aQute/lib/osgi/eclipse/EclipseClasspath.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/persistentmap/PersistentMap.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/persistentmap/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/settings/PasswordCryptor.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/settings/Settings.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/settings/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/strings/Strings.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/strings/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/tag/Tag.java
+ADDED RESOURCE OSGI-OPT/src/aQute/lib/tag/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/classdump/ClassDumper.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/classdump/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/command/Command.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/command/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/cryptography/Crypto.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/cryptography/Digest.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/cryptography/Digester.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/cryptography/Key.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/cryptography/MD5.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/cryptography/RSA.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/cryptography/SHA1.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/cryptography/SHA256.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/cryptography/Signer.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/cryptography/Verifier.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/cryptography/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/filelock/DirectoryLock.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/filelock/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/filters/AndFilter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/filters/Filter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/filters/LiteralFilter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/filters/NotFilter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/filters/Operator.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/filters/OrFilter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/filters/SimpleFilter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/filters/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/forker/Forker.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/forker/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/generics/Create.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/generics/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/glob/Glob.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/glob/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/map/MAP.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/map/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/qtokens/QuotedTokenizer.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/qtokens/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/reporter/Message.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/reporter/ReporterAdapter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/reporter/ReporterMessages.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/reporter/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/sed/Domain.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/sed/Replacer.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/sed/ReplacerAdapter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/sed/Sed.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/sed/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/tuple/ComparablePair.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/tuple/Pair.java
+ADDED RESOURCE OSGI-OPT/src/aQute/libg/tuple/packageinfo
+ADDED RESOURCE OSGI-OPT/src/aQute/service/reporter/Messages.java
+ADDED RESOURCE OSGI-OPT/src/aQute/service/reporter/Report.java
+ADDED RESOURCE OSGI-OPT/src/aQute/service/reporter/Reporter.java
+ADDED RESOURCE OSGI-OPT/src/aQute/service/reporter/packageinfo
+ADDED RESOURCE OSGI-OPT/src/org/osgi/resource/Capability.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/resource/Namespace.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/resource/Requirement.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/resource/Resource.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/resource/Wire.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/resource/Wiring.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/resource/package-info.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/resource/packageinfo
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/bindex/BundleIndexer.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/bindex/packageinfo
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/Activate.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/Component.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/ConfigurationPolicy.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/Deactivate.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/LookupReference.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/Modified.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/Reference.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/ReferenceCardinality.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/ReferencePolicy.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/ReferencePolicyOption.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/ReferenceScope.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/ServiceScope.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/package-info.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/component/annotations/packageinfo
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/coordinator/Coordination.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/coordinator/CoordinationException.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/coordinator/CoordinationPermission.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/coordinator/Coordinator.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/coordinator/Participant.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/coordinator/package-info.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/coordinator/packageinfo
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/repository/ContentNamespace.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/repository/Repository.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/repository/RepositoryContent.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/repository/package-info.java
+ADDED RESOURCE OSGI-OPT/src/org/osgi/service/repository/packageinfo
+ADDED RESOURCE aQute/bnd/annotation/ConsumerType.class
+MAJOR RESOURCE aQute/bnd/annotation/Export.class
+ ADDED SHA 1143580202AA1EED607C3B44C18C4FDDFF807556
+ REMOVED SHA 2BFAD4181827144A0B7BF70F1B50AFFFD1486DB0
+REMOVED RESOURCE aQute/bnd/annotation/Import.class
+REMOVED RESOURCE aQute/bnd/annotation/Pair.class
+ADDED RESOURCE aQute/bnd/annotation/ProviderType.class
+ADDED RESOURCE aQute/bnd/annotation/Version.class
+ADDED RESOURCE aQute/bnd/annotation/component/Activate.class
+ADDED RESOURCE aQute/bnd/annotation/component/Component.class
+ADDED RESOURCE aQute/bnd/annotation/component/ConfigurationPolicy.class
+ADDED RESOURCE aQute/bnd/annotation/component/Deactivate.class
+ADDED RESOURCE aQute/bnd/annotation/component/Modified.class
+ADDED RESOURCE aQute/bnd/annotation/component/Reference.class
+ADDED RESOURCE aQute/bnd/annotation/component/packageinfo
+ADDED RESOURCE aQute/bnd/annotation/headers/About.class
+ADDED RESOURCE aQute/bnd/annotation/headers/BundleCategory.class
+ADDED RESOURCE aQute/bnd/annotation/headers/BundleContributors.class
+ADDED RESOURCE aQute/bnd/annotation/headers/BundleCopyright.class
+ADDED RESOURCE aQute/bnd/annotation/headers/BundleDevelopers.class
+ADDED RESOURCE aQute/bnd/annotation/headers/BundleDocURL.class
+ADDED RESOURCE aQute/bnd/annotation/headers/BundleLicense.class
+ADDED RESOURCE aQute/bnd/annotation/headers/Category.class
+ADDED RESOURCE aQute/bnd/annotation/headers/ProvideCapability.class
+ADDED RESOURCE aQute/bnd/annotation/headers/RequireCapability.class
+ADDED RESOURCE aQute/bnd/annotation/headers/Resolution.class
+ADDED RESOURCE aQute/bnd/annotation/headers/packageinfo
+ADDED RESOURCE aQute/bnd/annotation/licenses/ASL_2_0.class
+ADDED RESOURCE aQute/bnd/annotation/licenses/BSD_2_Clause.class
+ADDED RESOURCE aQute/bnd/annotation/licenses/BSD_3_Clause.class
+ADDED RESOURCE aQute/bnd/annotation/licenses/CDDL_1_0.class
+ADDED RESOURCE aQute/bnd/annotation/licenses/CPL_1_0.class
+ADDED RESOURCE aQute/bnd/annotation/licenses/EPL_1_0.class
+ADDED RESOURCE aQute/bnd/annotation/licenses/GPL_2_0.class
+ADDED RESOURCE aQute/bnd/annotation/licenses/GPL_3_0.class
+ADDED RESOURCE aQute/bnd/annotation/licenses/LGPL_2_1.class
+ADDED RESOURCE aQute/bnd/annotation/licenses/MIT_1_0.class
+ADDED RESOURCE aQute/bnd/annotation/licenses/MPL_2_0.class
+ADDED RESOURCE aQute/bnd/annotation/licenses/packageinfo
+ADDED RESOURCE aQute/bnd/annotation/metatype/Configurable$ConfigurableHandler.class
+ADDED RESOURCE aQute/bnd/annotation/metatype/Configurable.class
+ADDED RESOURCE aQute/bnd/annotation/metatype/Meta$AD.class
+ADDED RESOURCE aQute/bnd/annotation/metatype/Meta$OCD.class
+ADDED RESOURCE aQute/bnd/annotation/metatype/Meta$Type.class
+ADDED RESOURCE aQute/bnd/annotation/metatype/Meta.class
+ADDED RESOURCE aQute/bnd/annotation/metatype/packageinfo
+REMOVED RESOURCE aQute/bnd/annotation/package-info.class
+ADDED RESOURCE aQute/bnd/annotation/packageinfo
+ADDED RESOURCE aQute/bnd/ant/AntMessages.class
+MAJOR RESOURCE aQute/bnd/ant/BaseTask.class
+ REMOVED SHA 51D09D3D464164F6ADC6362CA1B27B38DFFF7775
+ ADDED SHA B4D7F65A6C5B0945D0BBDD1AD45C628C69945A49
+MAJOR RESOURCE aQute/bnd/ant/BndTask.class
+ ADDED SHA 2B9158162896486E31EF06E4EAADE45EC4F928BD
+ REMOVED SHA 3D24F9D832A2CFCE08FB5B63A0787BF8D8B9C08F
+ADDED RESOURCE aQute/bnd/ant/ConsoleProgress$1.class
+ADDED RESOURCE aQute/bnd/ant/ConsoleProgress.class
+MAJOR RESOURCE aQute/bnd/ant/DeployTask.class
+ REMOVED SHA 52741E8FA833A6F5770C2E07C44926B51AA03FF4
+ ADDED SHA B1FD3738343C5BDDEDCD61C8F16C82151F1E5C38
+MAJOR RESOURCE aQute/bnd/ant/EclipseTask.class
+ REMOVED SHA 1EDD3A0BD19408E75550B699EFD8E605175A966C
+ ADDED SHA 3BAD8CC8DB9F4C3AD5D0BBFF0CA4471FBB1DC27A
+MAJOR RESOURCE aQute/bnd/ant/ExpandPropertiesTask.class
+ ADDED SHA 27A2EFF363C15BAF95BBF4FE0F4511F082CC89D7
+ REMOVED SHA 2EFC6B42F01F13BFACB93F0D794547749A2392BF
+ADDED RESOURCE aQute/bnd/ant/PackageTask.class
+MAJOR RESOURCE aQute/bnd/ant/PrepareTask.class
+ REMOVED SHA 54FE708015DC152FBA2AC912C5F43823F2297E11
+ ADDED SHA 605DCAD147113525F3FA93F47FC598AC49B35266
+ADDED RESOURCE aQute/bnd/ant/ProjectBuildOrderTask.class
+MAJOR RESOURCE aQute/bnd/ant/ProjectTask.class
+ REMOVED SHA B99801A0059530E09B4C378C6BFB19F7EC51FC87
+ ADDED SHA C80B65108386F8484624573016EB7B170743DD5E
+ADDED RESOURCE aQute/bnd/ant/ReleaseTask.class
+ADDED RESOURCE aQute/bnd/ant/RunconfigToDistributionTask$JarFileFilter.class
+ADDED RESOURCE aQute/bnd/ant/RunconfigToDistributionTask$NonTestProjectFileFilter.class
+ADDED RESOURCE aQute/bnd/ant/RunconfigToDistributionTask.class
+ADDED RESOURCE aQute/bnd/ant/TestTask.class
+MAJOR RESOURCE aQute/bnd/ant/WrapTask.class
+ REMOVED SHA 1B2451001E019453C391ADD866B2C6F447BB8634
+ ADDED SHA F7EF8607FD423257913E692044478BA62CBC23AA
+MAJOR RESOURCE aQute/bnd/ant/taskdef.properties
+ REMOVED SHA 1F5BB944D17010889C822CB49ACD790CA0B4BEAA
+ ADDED SHA BB9108C257261825D22A7E893516293707C87E20
+MAJOR RESOURCE aQute/bnd/build/CircularDependencyException.class
+ REMOVED SHA 76252D29BAB7D6C9EEE9CA30D7C487A214974E40
+ ADDED SHA D4B16B74592DDA7F7E3DC794FD2FEB062EFB2B45
+MAJOR RESOURCE aQute/bnd/build/Container$TYPE.class
+ ADDED SHA 098B23F977005C8CD012084BF095FB6F0A4EA49F
+ REMOVED SHA AE3A226139BCF586C92B7003D1961DA741CA1738
+MAJOR RESOURCE aQute/bnd/build/Container.class
+ REMOVED SHA 8458A19DB2BB531949466ED9DB5B72E310BAC81D
+ ADDED SHA D196DA5CE6E0FF6D680D279C3C1017656BB9A579
+ADDED RESOURCE aQute/bnd/build/DownloadBlocker$Stage.class
+ADDED RESOURCE aQute/bnd/build/DownloadBlocker.class
+ADDED RESOURCE aQute/bnd/build/ErrorDetails.class
+REMOVED RESOURCE aQute/bnd/build/Framework$FilterLoader.class
+REMOVED RESOURCE aQute/bnd/build/Framework.class
+ADDED RESOURCE aQute/bnd/build/JUnitLauncher.class
+ADDED RESOURCE aQute/bnd/build/Project$1.class
+ADDED RESOURCE aQute/bnd/build/Project$2.class
+MAJOR RESOURCE aQute/bnd/build/Project.class
+ ADDED SHA 6B8BC68C0CA5FB105A03813147E09DC02C236327
+ REMOVED SHA C5F1CBF529FFFBE114BB09F8D0609B586E61103A
+MAJOR RESOURCE aQute/bnd/build/ProjectBuilder.class
+ REMOVED SHA 2E8690B06114FC5ECF967371D0A9F92FA64D1B35
+ ADDED SHA 6997CF482327EECA249D215F9522CD051FB13952
+ADDED RESOURCE aQute/bnd/build/ProjectLauncher$1.class
+ADDED RESOURCE aQute/bnd/build/ProjectLauncher$NotificationListener.class
+ADDED RESOURCE aQute/bnd/build/ProjectLauncher$NotificationType.class
+ADDED RESOURCE aQute/bnd/build/ProjectLauncher.class
+ADDED RESOURCE aQute/bnd/build/ProjectMessages.class
+ADDED RESOURCE aQute/bnd/build/ProjectTester.class
+ADDED RESOURCE aQute/bnd/build/ReflectAction.class
+ADDED RESOURCE aQute/bnd/build/ResolverMode.class
+ADDED RESOURCE aQute/bnd/build/Run.class
+ADDED RESOURCE aQute/bnd/build/ScriptAction.class
+ADDED RESOURCE aQute/bnd/build/Workspace$CachedFileRepo.class
+MAJOR RESOURCE aQute/bnd/build/Workspace.class
+ ADDED SHA 53261A511EDC0A6939C7CC784F2929BE2D1A233C
+ REMOVED SHA C1E463E37C42CB254EC9EE5AFDB456108F01B263
+ADDED RESOURCE aQute/bnd/build/WorkspaceRepository.class
+ADDED RESOURCE aQute/bnd/build/defaults.bnd
+ADDED RESOURCE aQute/bnd/build/model/BndEditModel$1.class
+ADDED RESOURCE aQute/bnd/build/model/BndEditModel$2.class
+ADDED RESOURCE aQute/bnd/build/model/BndEditModel$3.class
+ADDED RESOURCE aQute/bnd/build/model/BndEditModel$4.class
+ADDED RESOURCE aQute/bnd/build/model/BndEditModel$5.class
+ADDED RESOURCE aQute/bnd/build/model/BndEditModel$6.class
+ADDED RESOURCE aQute/bnd/build/model/BndEditModel.class
+ADDED RESOURCE aQute/bnd/build/model/EE.class
+ADDED RESOURCE aQute/bnd/build/model/clauses/ComponentSvcReference.class
+ADDED RESOURCE aQute/bnd/build/model/clauses/ExportedPackage.class
+ADDED RESOURCE aQute/bnd/build/model/clauses/HeaderClause.class
+ADDED RESOURCE aQute/bnd/build/model/clauses/ImportPattern.class
+ADDED RESOURCE aQute/bnd/build/model/clauses/ServiceComponent.class
+ADDED RESOURCE aQute/bnd/build/model/clauses/VersionedClause.class
+ADDED RESOURCE aQute/bnd/build/model/clauses/packageinfo
+ADDED RESOURCE aQute/bnd/build/model/conversions/ClauseListConverter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/CollectionFormatter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/Converter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/DefaultBooleanFormatter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/DefaultFormatter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/EEConverter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/EEFormatter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/EnumConverter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/EnumFormatter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/HeaderClauseConverter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/HeaderClauseFormatter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/HeaderClauseListConverter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/MapFormatter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/NewlineEscapedStringFormatter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/NoopConverter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/PropertiesConverter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/PropertiesEntryFormatter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/RequirementFormatter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/RequirementListConverter$1.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/RequirementListConverter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/SimpleListConverter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/StringEntryConverter.class
+ADDED RESOURCE aQute/bnd/build/model/conversions/VersionedClauseConverter.class
+ADDED RESOURCE aQute/bnd/build/model/packageinfo
+ADDED RESOURCE aQute/bnd/build/packageinfo
+REMOVED RESOURCE aQute/bnd/classpath/BndContainer.class
+REMOVED RESOURCE aQute/bnd/classpath/BndContainerInitializer.class
+REMOVED RESOURCE aQute/bnd/classpath/BndContainerPage$1.class
+REMOVED RESOURCE aQute/bnd/classpath/BndContainerPage$2.class
+REMOVED RESOURCE aQute/bnd/classpath/BndContainerPage$3.class
+REMOVED RESOURCE aQute/bnd/classpath/BndContainerPage.class
+REMOVED RESOURCE aQute/bnd/classpath/ModelListener.class
+REMOVED RESOURCE aQute/bnd/classpath/messages.properties
+ADDED RESOURCE aQute/bnd/compatibility/Access.class
+ADDED RESOURCE aQute/bnd/compatibility/GenericParameter.class
+ADDED RESOURCE aQute/bnd/compatibility/GenericType$GenericArray.class
+ADDED RESOURCE aQute/bnd/compatibility/GenericType$GenericWildcard.class
+ADDED RESOURCE aQute/bnd/compatibility/GenericType.class
+ADDED RESOURCE aQute/bnd/compatibility/Kind.class
+ADDED RESOURCE aQute/bnd/compatibility/ParseSignatureBuilder$1.class
+ADDED RESOURCE aQute/bnd/compatibility/ParseSignatureBuilder.class
+ADDED RESOURCE aQute/bnd/compatibility/RuntimeSignatureBuilder.class
+ADDED RESOURCE aQute/bnd/compatibility/Scope.class
+ADDED RESOURCE aQute/bnd/compatibility/Signatures$Rover.class
+ADDED RESOURCE aQute/bnd/compatibility/Signatures.class
+ADDED RESOURCE aQute/bnd/component/AnnotationReader$1.class
+ADDED RESOURCE aQute/bnd/component/AnnotationReader.class
+ADDED RESOURCE aQute/bnd/component/ComponentDef.class
+ADDED RESOURCE aQute/bnd/component/DSAnnotations.class
+ADDED RESOURCE aQute/bnd/component/HeaderReader$1.class
+ADDED RESOURCE aQute/bnd/component/HeaderReader.class
+ADDED RESOURCE aQute/bnd/component/ReferenceDef.class
+ADDED RESOURCE aQute/bnd/component/TagResource.class
+ADDED RESOURCE aQute/bnd/component/error/DeclarativeServicesAnnotationError$ErrorType.class
+ADDED RESOURCE aQute/bnd/component/error/DeclarativeServicesAnnotationError.class
+ADDED RESOURCE aQute/bnd/component/error/packageinfo
+ADDED RESOURCE aQute/bnd/differ/Baseline$1.class
+ADDED RESOURCE aQute/bnd/differ/Baseline$BundleInfo.class
+ADDED RESOURCE aQute/bnd/differ/Baseline$Info.class
+ADDED RESOURCE aQute/bnd/differ/Baseline.class
+ADDED RESOURCE aQute/bnd/differ/DiffImpl.class
+ADDED RESOURCE aQute/bnd/differ/DiffPluginImpl.class
+ADDED RESOURCE aQute/bnd/differ/Element.class
+ADDED RESOURCE aQute/bnd/differ/JavaElement$1.class
+ADDED RESOURCE aQute/bnd/differ/JavaElement.class
+ADDED RESOURCE aQute/bnd/differ/RepositoryElement.class
+ADDED RESOURCE aQute/bnd/differ/packageinfo
+ADDED RESOURCE aQute/bnd/enroute/commands/EnrouteCommand$WorkspaceOptions.class
+ADDED RESOURCE aQute/bnd/enroute/commands/EnrouteCommand.class
+ADDED RESOURCE aQute/bnd/enroute/commands/EnrouteOptions.class
+ADDED RESOURCE aQute/bnd/filerepo/FileRepo$1.class
+ADDED RESOURCE aQute/bnd/filerepo/FileRepo$2.class
+ADDED RESOURCE aQute/bnd/filerepo/FileRepo$3.class
+ADDED RESOURCE aQute/bnd/filerepo/FileRepo.class
+ADDED RESOURCE aQute/bnd/filerepo/packageinfo
+ADDED RESOURCE aQute/bnd/gradle/BndPlugin.gradle
+ADDED RESOURCE aQute/bnd/header/Attrs$1.class
+ADDED RESOURCE aQute/bnd/header/Attrs$2.class
+ADDED RESOURCE aQute/bnd/header/Attrs$3.class
+ADDED RESOURCE aQute/bnd/header/Attrs$4.class
+ADDED RESOURCE aQute/bnd/header/Attrs$5.class
+ADDED RESOURCE aQute/bnd/header/Attrs$6.class
+ADDED RESOURCE aQute/bnd/header/Attrs$7.class
+ADDED RESOURCE aQute/bnd/header/Attrs$8.class
+ADDED RESOURCE aQute/bnd/header/Attrs$DataType.class
+ADDED RESOURCE aQute/bnd/header/Attrs$Type.class
+ADDED RESOURCE aQute/bnd/header/Attrs.class
+ADDED RESOURCE aQute/bnd/header/OSGiHeader.class
+ADDED RESOURCE aQute/bnd/header/Parameters.class
+ADDED RESOURCE aQute/bnd/header/packageinfo
+ADDED RESOURCE aQute/bnd/help/Syntax.class
+ADDED RESOURCE aQute/bnd/help/Warnings.class
+ADDED RESOURCE aQute/bnd/help/changed.txt
+ADDED RESOURCE aQute/bnd/help/packageinfo
+ADDED RESOURCE aQute/bnd/help/syntax.properties
+ADDED RESOURCE aQute/bnd/help/syntax.xml
+ADDED RESOURCE aQute/bnd/indexer/MimeType.class
+ADDED RESOURCE aQute/bnd/indexer/NOTE
+ADDED RESOURCE aQute/bnd/indexer/Namespaces.class
+ADDED RESOURCE aQute/bnd/indexer/RepoIndex$IndexResult.class
+ADDED RESOURCE aQute/bnd/indexer/RepoIndex.class
+ADDED RESOURCE aQute/bnd/indexer/ResourceAnalyzer.class
+ADDED RESOURCE aQute/bnd/indexer/analyzers/BlueprintAnalyzer.class
+ADDED RESOURCE aQute/bnd/indexer/analyzers/BundleAnalyzer$1.class
+ADDED RESOURCE aQute/bnd/indexer/analyzers/BundleAnalyzer$2.class
+ADDED RESOURCE aQute/bnd/indexer/analyzers/BundleAnalyzer.class
+ADDED RESOURCE aQute/bnd/indexer/analyzers/EE$Segment.class
+ADDED RESOURCE aQute/bnd/indexer/analyzers/EE.class
+ADDED RESOURCE aQute/bnd/indexer/analyzers/KnownBundleAnalyzer.class
+ADDED RESOURCE aQute/bnd/indexer/analyzers/NOTE
+ADDED RESOURCE aQute/bnd/indexer/analyzers/OSGiFrameworkAnalyzer.class
+ADDED RESOURCE aQute/bnd/indexer/analyzers/SCRAnalyzer$SCRContentHandler.class
+ADDED RESOURCE aQute/bnd/indexer/analyzers/SCRAnalyzer.class
+ADDED RESOURCE aQute/bnd/indexer/analyzers/Yield.class
+ADDED RESOURCE aQute/bnd/indexer/analyzers/known-bundles.properties
+REMOVED RESOURCE aQute/bnd/jareditor/JarConfiguration.class
+REMOVED RESOURCE aQute/bnd/jareditor/JarDocumentProvider.class
+REMOVED RESOURCE aQute/bnd/jareditor/JarEditor.class
+REMOVED RESOURCE aQute/bnd/junit/OSGiArgumentsTab.class
+REMOVED RESOURCE aQute/bnd/junit/OSGiJUnitLaunchShortcut.class
+REMOVED RESOURCE aQute/bnd/junit/OSGiJUnitLauncherConfigurationDelegate.class
+REMOVED RESOURCE aQute/bnd/junit/OSGiJUnitTabGroup.class
+REMOVED RESOURCE aQute/bnd/launch/LaunchDelegate.class
+REMOVED RESOURCE aQute/bnd/launch/LaunchTabGroup.class
+REMOVED RESOURCE aQute/bnd/launch/Shortcut.class
+ADDED RESOURCE aQute/bnd/main/BaselineCommands$1.class
+ADDED RESOURCE aQute/bnd/main/BaselineCommands$PSpec.class
+ADDED RESOURCE aQute/bnd/main/BaselineCommands$baseLineOptions.class
+ADDED RESOURCE aQute/bnd/main/BaselineCommands$schemaOptions.class
+ADDED RESOURCE aQute/bnd/main/BaselineCommands.class
+ADDED RESOURCE aQute/bnd/main/BndMessages.class
+ADDED RESOURCE aQute/bnd/main/DiffCommand$diffOptions.class
+ADDED RESOURCE aQute/bnd/main/DiffCommand.class
+ADDED RESOURCE aQute/bnd/main/PatchCommand$applyOptions.class
+ADDED RESOURCE aQute/bnd/main/PatchCommand$createOptions.class
+ADDED RESOURCE aQute/bnd/main/PatchCommand.class
+ADDED RESOURCE aQute/bnd/main/RepoCommand$1Spec.class
+ADDED RESOURCE aQute/bnd/main/RepoCommand$CopyOptions.class
+ADDED RESOURCE aQute/bnd/main/RepoCommand$RefreshOptions.class
+ADDED RESOURCE aQute/bnd/main/RepoCommand$VersionsOptions.class
+ADDED RESOURCE aQute/bnd/main/RepoCommand$diffOptions.class
+ADDED RESOURCE aQute/bnd/main/RepoCommand$getOptions.class
+ADDED RESOURCE aQute/bnd/main/RepoCommand$listOptions.class
+ADDED RESOURCE aQute/bnd/main/RepoCommand$putOptions.class
+ADDED RESOURCE aQute/bnd/main/RepoCommand$repoOptions.class
+ADDED RESOURCE aQute/bnd/main/RepoCommand$reposOptions.class
+ADDED RESOURCE aQute/bnd/main/RepoCommand.class
+ADDED RESOURCE aQute/bnd/main/bnd$1.class
+ADDED RESOURCE aQute/bnd/main/bnd$2.class
+ADDED RESOURCE aQute/bnd/main/bnd$ActionOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$Alg.class
+ADDED RESOURCE aQute/bnd/main/bnd$All.class
+ADDED RESOURCE aQute/bnd/main/bnd$BootstrapOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$Bsn2UrlOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$ChangesOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$EEOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$FindOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$MergeOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$ParallelBuildOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$bndOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$buildoptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$buildxOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$bumpoptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$cleanOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$convertOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$createOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$debugOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$deliverableOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$dooptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$eclipseOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$extractOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$grepOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$hashOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$infoOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$macroOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$packageOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$patchOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$printOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$projectOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$releaseOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$runOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$runtestsOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$selectOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$settingOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$sourceOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$syntaxOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$testOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$typeOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$verifyOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$versionOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$viewOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$wrapOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd$xrefOptions.class
+ADDED RESOURCE aQute/bnd/main/bnd-completion.bash
+MAJOR RESOURCE aQute/bnd/main/bnd.class
+ REMOVED SHA EC8E2F8B6765E64C5D418653DC3871A6DA71844F
+ ADDED SHA FF3A6531C0DD6E4E5CD3D729560D599EB929AF89
+ADDED RESOURCE aQute/bnd/main/component.xslt
+ADDED RESOURCE aQute/bnd/main/maven-settings.xsl
+MAJOR RESOURCE aQute/bnd/main/packageinfo
+ REMOVED SHA 4CBF1AE09B541F925C0D6BCBB8684EF85A0B8373
+ ADDED SHA D6E9A3759405E18A266202C40D9AA4EBA6F83A87
+ADDED RESOURCE aQute/bnd/main/schema.xsl
+ADDED RESOURCE aQute/bnd/main/testreport.xsl
+MAJOR RESOURCE aQute/bnd/make/Make.class
+ REMOVED SHA C450A93D92842FFF73F919416D67D8566529ECD2
+ ADDED SHA F248C429BED7224A1B11ADAB230FF812487D41E2
+MAJOR RESOURCE aQute/bnd/make/MakeBnd.class
+ ADDED SHA B3A3364451F9DC3A4E091C47EA9D85E345B6F6F1
+ REMOVED SHA F255AC80281C5577080A25A0E0F2C1CEF36C53E9
+MAJOR RESOURCE aQute/bnd/make/MakeCopy.class
+ REMOVED SHA 1B9DD05EB5FF75A176E39BA6B3D23D8FA1610953
+ ADDED SHA C99F91A71B005FBA46F74B265A12475BA7B056AC
+ADDED RESOURCE aQute/bnd/make/calltree/CalltreeResource$1.class
+ADDED RESOURCE aQute/bnd/make/calltree/CalltreeResource$2.class
+ADDED RESOURCE aQute/bnd/make/calltree/CalltreeResource.class
+ADDED RESOURCE aQute/bnd/make/component/ComponentAnnotationReader.class
+ADDED RESOURCE aQute/bnd/make/component/ServiceComponent$ComponentMaker.class
+ADDED RESOURCE aQute/bnd/make/component/ServiceComponent.class
+ADDED RESOURCE aQute/bnd/make/coverage/Coverage$1.class
+ADDED RESOURCE aQute/bnd/make/coverage/Coverage$2.class
+ADDED RESOURCE aQute/bnd/make/coverage/Coverage$3.class
+ADDED RESOURCE aQute/bnd/make/coverage/Coverage.class
+ADDED RESOURCE aQute/bnd/make/coverage/CoverageResource.class
+ADDED RESOURCE aQute/bnd/make/metatype/MetaTypeReader$1.class
+ADDED RESOURCE aQute/bnd/make/metatype/MetaTypeReader$Find.class
+ADDED RESOURCE aQute/bnd/make/metatype/MetaTypeReader.class
+ADDED RESOURCE aQute/bnd/make/metatype/MetatypePlugin.class
+ADDED RESOURCE aQute/bnd/maven/BsnToMavenPath.class
+ADDED RESOURCE aQute/bnd/maven/MavenCommand.class
+ADDED RESOURCE aQute/bnd/maven/MavenDependencyGraph$Artifact.class
+ADDED RESOURCE aQute/bnd/maven/MavenDependencyGraph$Scope.class
+ADDED RESOURCE aQute/bnd/maven/MavenDependencyGraph.class
+ADDED RESOURCE aQute/bnd/maven/MavenDeploy.class
+ADDED RESOURCE aQute/bnd/maven/MavenDeployCmd.class
+ADDED RESOURCE aQute/bnd/maven/MavenGroup.class
+ADDED RESOURCE aQute/bnd/maven/MavenRepository.class
+ADDED RESOURCE aQute/bnd/maven/PomFromManifest.class
+ADDED RESOURCE aQute/bnd/maven/PomParser.class
+ADDED RESOURCE aQute/bnd/maven/PomResource.class
+ADDED RESOURCE aQute/bnd/maven/support/CachedPom.class
+ADDED RESOURCE aQute/bnd/maven/support/Maven.class
+ADDED RESOURCE aQute/bnd/maven/support/MavenEntry$1.class
+ADDED RESOURCE aQute/bnd/maven/support/MavenEntry$2.class
+ADDED RESOURCE aQute/bnd/maven/support/MavenEntry.class
+ADDED RESOURCE aQute/bnd/maven/support/MavenRemoteRepository.class
+ADDED RESOURCE aQute/bnd/maven/support/Pom$Dependency.class
+ADDED RESOURCE aQute/bnd/maven/support/Pom$Rover.class
+ADDED RESOURCE aQute/bnd/maven/support/Pom$Scope.class
+ADDED RESOURCE aQute/bnd/maven/support/Pom.class
+ADDED RESOURCE aQute/bnd/maven/support/ProjectPom$Rover.class
+ADDED RESOURCE aQute/bnd/maven/support/ProjectPom.class
+ADDED RESOURCE aQute/bnd/maven/support/Repo.class
+ADDED RESOURCE aQute/bnd/maven/support/packageinfo
+ADDED RESOURCE aQute/bnd/obr/OBRFragment.class
+ADDED RESOURCE aQute/bnd/osgi/About.class
+ADDED RESOURCE aQute/bnd/osgi/AbstractResource.class
+ADDED RESOURCE aQute/bnd/osgi/Analyzer$1.class
+ADDED RESOURCE aQute/bnd/osgi/Analyzer$2.class
+ADDED RESOURCE aQute/bnd/osgi/Analyzer.class
+ADDED RESOURCE aQute/bnd/osgi/AnalyzerMessages.class
+ADDED RESOURCE aQute/bnd/osgi/Annotation.class
+ADDED RESOURCE aQute/bnd/osgi/AnnotationHeaders.class
+ADDED RESOURCE aQute/bnd/osgi/Builder.class
+ADDED RESOURCE aQute/bnd/osgi/BundleId.class
+ADDED RESOURCE aQute/bnd/osgi/ClassDataCollector.class
+ADDED RESOURCE aQute/bnd/osgi/ClassDataCollectors.class
+ADDED RESOURCE aQute/bnd/osgi/Classpath$ClassVisitor.class
+ADDED RESOURCE aQute/bnd/osgi/Classpath.class
+ADDED RESOURCE aQute/bnd/osgi/Clazz$1.class
+ADDED RESOURCE aQute/bnd/osgi/Clazz$Assoc.class
+ADDED RESOURCE aQute/bnd/osgi/Clazz$ClassConstant.class
+ADDED RESOURCE aQute/bnd/osgi/Clazz$Def.class
+ADDED RESOURCE aQute/bnd/osgi/Clazz$FieldDef.class
+ADDED RESOURCE aQute/bnd/osgi/Clazz$JAVA$1.class
+ADDED RESOURCE aQute/bnd/osgi/Clazz$JAVA.class
+ADDED RESOURCE aQute/bnd/osgi/Clazz$MethodDef.class
+ADDED RESOURCE aQute/bnd/osgi/Clazz$QUERY.class
+ADDED RESOURCE aQute/bnd/osgi/Clazz$TypeDef.class
+ADDED RESOURCE aQute/bnd/osgi/Clazz.class
+ADDED RESOURCE aQute/bnd/osgi/CombinedResource$1.class
+ADDED RESOURCE aQute/bnd/osgi/CombinedResource.class
+ADDED RESOURCE aQute/bnd/osgi/CommandResource.class
+ADDED RESOURCE aQute/bnd/osgi/Constants.class
+ADDED RESOURCE aQute/bnd/osgi/Contracts$Contract.class
+ADDED RESOURCE aQute/bnd/osgi/Contracts.class
+ADDED RESOURCE aQute/bnd/osgi/Descriptors$ArrayRef.class
+ADDED RESOURCE aQute/bnd/osgi/Descriptors$ConcreteRef.class
+ADDED RESOURCE aQute/bnd/osgi/Descriptors$Descriptor.class
+ADDED RESOURCE aQute/bnd/osgi/Descriptors$PackageRef.class
+ADDED RESOURCE aQute/bnd/osgi/Descriptors$Signature.class
+ADDED RESOURCE aQute/bnd/osgi/Descriptors$SignatureType.class
+ADDED RESOURCE aQute/bnd/osgi/Descriptors$TypeRef.class
+ADDED RESOURCE aQute/bnd/osgi/Descriptors.class
+ADDED RESOURCE aQute/bnd/osgi/Domain$1$1.class
+ADDED RESOURCE aQute/bnd/osgi/Domain$1.class
+ADDED RESOURCE aQute/bnd/osgi/Domain$2$1.class
+ADDED RESOURCE aQute/bnd/osgi/Domain$2.class
+ADDED RESOURCE aQute/bnd/osgi/Domain$3.class
+ADDED RESOURCE aQute/bnd/osgi/Domain.class
+ADDED RESOURCE aQute/bnd/osgi/EmbeddedResource.class
+ADDED RESOURCE aQute/bnd/osgi/FileResource.class
+ADDED RESOURCE aQute/bnd/osgi/Instruction$Filter.class
+ADDED RESOURCE aQute/bnd/osgi/Instruction.class
+ADDED RESOURCE aQute/bnd/osgi/Instructions.class
+ADDED RESOURCE aQute/bnd/osgi/Jar$Compression.class
+ADDED RESOURCE aQute/bnd/osgi/Jar.class
+ADDED RESOURCE aQute/bnd/osgi/JarResource.class
+ADDED RESOURCE aQute/bnd/osgi/Macro$1.class
+ADDED RESOURCE aQute/bnd/osgi/Macro$Link.class
+ADDED RESOURCE aQute/bnd/osgi/Macro.class
+ADDED RESOURCE aQute/bnd/osgi/OSInformation.class
+ADDED RESOURCE aQute/bnd/osgi/OpCodes.class
+ADDED RESOURCE aQute/bnd/osgi/Packages.class
+ADDED RESOURCE aQute/bnd/osgi/PreprocessResource.class
+ADDED RESOURCE aQute/bnd/osgi/Processor$1.class
+ADDED RESOURCE aQute/bnd/osgi/Processor$CL.class
+ADDED RESOURCE aQute/bnd/osgi/Processor$FileLine.class
+ADDED RESOURCE aQute/bnd/osgi/Processor$SetLocationImpl.class
+ADDED RESOURCE aQute/bnd/osgi/Processor.class
+ADDED RESOURCE aQute/bnd/osgi/Resource.class
+ADDED RESOURCE aQute/bnd/osgi/URLResource.class
+ADDED RESOURCE aQute/bnd/osgi/Verifier$ActivatorErrorType.class
+ADDED RESOURCE aQute/bnd/osgi/Verifier$BundleActivatorError.class
+ADDED RESOURCE aQute/bnd/osgi/Verifier$EE.class
+ADDED RESOURCE aQute/bnd/osgi/Verifier.class
+ADDED RESOURCE aQute/bnd/osgi/WriteResource$1.class
+ADDED RESOURCE aQute/bnd/osgi/WriteResource$CountingOutputStream.class
+ADDED RESOURCE aQute/bnd/osgi/WriteResource.class
+ADDED RESOURCE aQute/bnd/osgi/ZipResource.class
+ADDED RESOURCE aQute/bnd/osgi/bnd.info
+ADDED RESOURCE aQute/bnd/osgi/eclipse/EclipseClasspath.class
+ADDED RESOURCE aQute/bnd/osgi/packageinfo
+ADDED RESOURCE aQute/bnd/osgi/profiles-OpenJDK8.properties
+ADDED RESOURCE aQute/bnd/osgi/resource/CapReq$MODE.class
+ADDED RESOURCE aQute/bnd/osgi/resource/CapReq.class
+ADDED RESOURCE aQute/bnd/osgi/resource/CapReqBuilder.class
+ADDED RESOURCE aQute/bnd/osgi/resource/CapabilityImpl.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$1.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$And.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$ApproximateExpression.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$BundleExpression.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$Expression$1.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$Expression$2.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$Expression.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$HostExpression.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$IdentityExpression.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$Not.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$Op.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$Or.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$PackageExpression.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$PatternExpression.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$RangeExpression.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$Rover.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$SimpleExpression.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$SubExpression.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser$WithRangeExpression.class
+ADDED RESOURCE aQute/bnd/osgi/resource/FilterParser.class
+ADDED RESOURCE aQute/bnd/osgi/resource/Filters.class
+ADDED RESOURCE aQute/bnd/osgi/resource/PersistentResource$Attr.class
+ADDED RESOURCE aQute/bnd/osgi/resource/PersistentResource$DataType.class
+ADDED RESOURCE aQute/bnd/osgi/resource/PersistentResource$Namespace.class
+ADDED RESOURCE aQute/bnd/osgi/resource/PersistentResource$RC.class
+ADDED RESOURCE aQute/bnd/osgi/resource/PersistentResource$RCData.class
+ADDED RESOURCE aQute/bnd/osgi/resource/PersistentResource.class
+ADDED RESOURCE aQute/bnd/osgi/resource/RequirementImpl.class
+ADDED RESOURCE aQute/bnd/osgi/resource/ResourceBuilder.class
+ADDED RESOURCE aQute/bnd/osgi/resource/ResourceImpl.class
+ADDED RESOURCE aQute/bnd/osgi/resource/packageinfo
+REMOVED RESOURCE aQute/bnd/plugin/Activator$1.class
+REMOVED RESOURCE aQute/bnd/plugin/Activator$2.class
+REMOVED RESOURCE aQute/bnd/plugin/Activator$3.class
+REMOVED RESOURCE aQute/bnd/plugin/Activator.class
+REMOVED RESOURCE aQute/bnd/plugin/Central$1.class
+REMOVED RESOURCE aQute/bnd/plugin/Central.class
+REMOVED RESOURCE aQute/bnd/plugin/builder/BndBuilder$DeltaVisitor.class
+REMOVED RESOURCE aQute/bnd/plugin/builder/BndBuilder$ResourceVisitor.class
+REMOVED RESOURCE aQute/bnd/plugin/builder/BndBuilder.class
+REMOVED RESOURCE aQute/bnd/plugin/builder/BndNature.class
+REMOVED RESOURCE aQute/bnd/plugin/builder/ToggleNatureAction.class
+REMOVED RESOURCE aQute/bnd/plugin/popup/actions/InstallBundle.class
+REMOVED RESOURCE aQute/bnd/plugin/popup/actions/MakeBundle.class
+REMOVED RESOURCE aQute/bnd/plugin/popup/actions/VerifyBundle.class
+REMOVED RESOURCE aQute/bnd/plugin/popup/actions/WrapBundle.class
+ADDED RESOURCE aQute/bnd/properties/BadLocationException.class
+ADDED RESOURCE aQute/bnd/properties/CopyOnWriteTextStore$StringTextStore.class
+ADDED RESOURCE aQute/bnd/properties/CopyOnWriteTextStore.class
+ADDED RESOURCE aQute/bnd/properties/Document$DelimiterInfo.class
+ADDED RESOURCE aQute/bnd/properties/Document.class
+ADDED RESOURCE aQute/bnd/properties/GapTextStore.class
+ADDED RESOURCE aQute/bnd/properties/IDocument.class
+ADDED RESOURCE aQute/bnd/properties/IRegion.class
+ADDED RESOURCE aQute/bnd/properties/ITextStore.class
+ADDED RESOURCE aQute/bnd/properties/Line.class
+ADDED RESOURCE aQute/bnd/properties/LineTracker.class
+ADDED RESOURCE aQute/bnd/properties/LineType.class
+ADDED RESOURCE aQute/bnd/properties/PropertiesLineReader.class
+ADDED RESOURCE aQute/bnd/properties/Region.class
+ADDED RESOURCE aQute/bnd/properties/packageinfo
+ADDED RESOURCE aQute/bnd/resource/repository/ResourceDescriptorImpl.class
+ADDED RESOURCE aQute/bnd/resource/repository/ResourceRepositoryImpl$1.class
+ADDED RESOURCE aQute/bnd/resource/repository/ResourceRepositoryImpl$2.class
+ADDED RESOURCE aQute/bnd/resource/repository/ResourceRepositoryImpl$FileLayout.class
+ADDED RESOURCE aQute/bnd/resource/repository/ResourceRepositoryImpl.class
+ADDED RESOURCE aQute/bnd/service/Actionable.class
+MAJOR RESOURCE aQute/bnd/service/AnalyzerPlugin.class
+ REMOVED SHA 6174C960699993F1F85880FE856A711616BDE67E
+ ADDED SHA 627FCDE5472BC5042106CCF06CBD3080BEA7B30D
+ADDED RESOURCE aQute/bnd/service/BndListener.class
+ADDED RESOURCE aQute/bnd/service/CommandPlugin.class
+ADDED RESOURCE aQute/bnd/service/Compiler.class
+ADDED RESOURCE aQute/bnd/service/DependencyContributor.class
+ADDED RESOURCE aQute/bnd/service/Deploy.class
+ADDED RESOURCE aQute/bnd/service/EclipseJUnitTester.class
+ADDED RESOURCE aQute/bnd/service/IndexProvider.class
+ADDED RESOURCE aQute/bnd/service/LauncherPlugin.class
+MAJOR RESOURCE aQute/bnd/service/MakePlugin.class
+ REMOVED SHA 6CBEC0D0266E75F5B416D933C8DB96BAD3F11858
+ ADDED SHA 862ADC2AD2ED8E88CEE4F668F281B41C7883E53A
+ADDED RESOURCE aQute/bnd/service/Plugin.class
+ADDED RESOURCE aQute/bnd/service/Refreshable.class
+ADDED RESOURCE aQute/bnd/service/Registry.class
+ADDED RESOURCE aQute/bnd/service/RegistryDonePlugin.class
+ADDED RESOURCE aQute/bnd/service/RegistryPlugin.class
+ADDED RESOURCE aQute/bnd/service/RemoteRepositoryPlugin.class
+ADDED RESOURCE aQute/bnd/service/RepositoryListenerPlugin.class
+ADDED RESOURCE aQute/bnd/service/RepositoryPlugin$DownloadListener.class
+ADDED RESOURCE aQute/bnd/service/RepositoryPlugin$PutOptions.class
+ADDED RESOURCE aQute/bnd/service/RepositoryPlugin$PutResult.class
+MAJOR RESOURCE aQute/bnd/service/RepositoryPlugin.class
+ ADDED SHA 7865276EC971AD9FFACB3A296DECE9E4C9A4A03F
+ REMOVED SHA 957238133AA5116ABC5415641ACE3D087FFD44BE
+ADDED RESOURCE aQute/bnd/service/ResolutionPhase.class
+ADDED RESOURCE aQute/bnd/service/ResourceHandle$Location.class
+ADDED RESOURCE aQute/bnd/service/ResourceHandle.class
+ADDED RESOURCE aQute/bnd/service/Scripter.class
+ADDED RESOURCE aQute/bnd/service/SignerPlugin.class
+ADDED RESOURCE aQute/bnd/service/Strategy.class
+ADDED RESOURCE aQute/bnd/service/action/Action.class
+ADDED RESOURCE aQute/bnd/service/action/NamedAction.class
+ADDED RESOURCE aQute/bnd/service/action/packageinfo
+ADDED RESOURCE aQute/bnd/service/classparser/ClassParser.class
+ADDED RESOURCE aQute/bnd/service/classparser/packageinfo
+ADDED RESOURCE aQute/bnd/service/diff/Delta.class
+ADDED RESOURCE aQute/bnd/service/diff/Diff$Data.class
+ADDED RESOURCE aQute/bnd/service/diff/Diff$Ignore.class
+ADDED RESOURCE aQute/bnd/service/diff/Diff.class
+ADDED RESOURCE aQute/bnd/service/diff/Differ.class
+ADDED RESOURCE aQute/bnd/service/diff/Tree$Data.class
+ADDED RESOURCE aQute/bnd/service/diff/Tree.class
+ADDED RESOURCE aQute/bnd/service/diff/Type.class
+ADDED RESOURCE aQute/bnd/service/diff/packageinfo
+ADDED RESOURCE aQute/bnd/service/extension/ExtensionActivator.class
+ADDED RESOURCE aQute/bnd/service/extension/packageinfo
+ADDED RESOURCE aQute/bnd/service/packageinfo
+ADDED RESOURCE aQute/bnd/service/progress/ProgressPlugin$Task.class
+ADDED RESOURCE aQute/bnd/service/progress/ProgressPlugin.class
+ADDED RESOURCE aQute/bnd/service/progress/packageinfo
+ADDED RESOURCE aQute/bnd/service/repository/InfoRepository.class
+ADDED RESOURCE aQute/bnd/service/repository/MinimalRepository$Gestalt.class
+ADDED RESOURCE aQute/bnd/service/repository/MinimalRepository.class
+ADDED RESOURCE aQute/bnd/service/repository/Phase.class
+ADDED RESOURCE aQute/bnd/service/repository/RepositoryDigest.class
+ADDED RESOURCE aQute/bnd/service/repository/ResourceRepository$Listener.class
+ADDED RESOURCE aQute/bnd/service/repository/ResourceRepository$ResourceRepositoryEvent.class
+ADDED RESOURCE aQute/bnd/service/repository/ResourceRepository$TYPE.class
+ADDED RESOURCE aQute/bnd/service/repository/ResourceRepository.class
+ADDED RESOURCE aQute/bnd/service/repository/SearchableRepository$ResourceDescriptor.class
+ADDED RESOURCE aQute/bnd/service/repository/SearchableRepository.class
+ADDED RESOURCE aQute/bnd/service/repository/packageinfo
+ADDED RESOURCE aQute/bnd/service/resolve/hook/ResolverHook.class
+ADDED RESOURCE aQute/bnd/service/resolve/hook/packageinfo
+ADDED RESOURCE aQute/bnd/service/url/TaggedData.class
+ADDED RESOURCE aQute/bnd/service/url/URLConnectionHandler.class
+ADDED RESOURCE aQute/bnd/service/url/URLConnector.class
+ADDED RESOURCE aQute/bnd/service/url/packageinfo
+ADDED RESOURCE aQute/bnd/signing/JartoolSigner$1.class
+ADDED RESOURCE aQute/bnd/signing/JartoolSigner.class
+ADDED RESOURCE aQute/bnd/signing/Signer.class
+REMOVED RESOURCE aQute/bnd/test/ProjectLauncher$1.class
+REMOVED RESOURCE aQute/bnd/test/ProjectLauncher$Streamer.class
+REMOVED RESOURCE aQute/bnd/test/ProjectLauncher.class
+REMOVED RESOURCE aQute/bnd/test/aQute.runtime.jar
+ADDED RESOURCE aQute/bnd/testing/DSTestWiring$Component.class
+ADDED RESOURCE aQute/bnd/testing/DSTestWiring$Reference.class
+ADDED RESOURCE aQute/bnd/testing/DSTestWiring.class
+ADDED RESOURCE aQute/bnd/testing/TestingLog$1.class
+ADDED RESOURCE aQute/bnd/testing/TestingLog$Config.class
+ADDED RESOURCE aQute/bnd/testing/TestingLog.class
+ADDED RESOURCE aQute/bnd/testing/packageinfo
+ADDED RESOURCE aQute/bnd/url/BasicAuthentication.class
+ADDED RESOURCE aQute/bnd/url/BndAuthentication.class
+ADDED RESOURCE aQute/bnd/url/ConnectionSettings$Config.class
+ADDED RESOURCE aQute/bnd/url/ConnectionSettings.class
+ADDED RESOURCE aQute/bnd/url/DefaultURLConnectionHandler.class
+ADDED RESOURCE aQute/bnd/url/HttpsVerification$1.class
+ADDED RESOURCE aQute/bnd/url/HttpsVerification$2.class
+ADDED RESOURCE aQute/bnd/url/HttpsVerification.class
+ADDED RESOURCE aQute/bnd/url/MultiURLConnectionHandler.class
+ADDED RESOURCE aQute/bnd/url/packageinfo
+ADDED RESOURCE aQute/bnd/util/dto/DTO.class
+ADDED RESOURCE aQute/bnd/util/dto/packageinfo
+ADDED RESOURCE aQute/bnd/version/Version.class
+ADDED RESOURCE aQute/bnd/version/VersionRange.class
+ADDED RESOURCE aQute/bnd/version/packageinfo
+ADDED RESOURCE aQute/configurable/Config.class
+ADDED RESOURCE aQute/configurable/Configurable$ConfigurableHandler.class
+ADDED RESOURCE aQute/configurable/Configurable.class
+ADDED RESOURCE aQute/configurable/packageinfo
+ADDED RESOURCE aQute/lib/base64/Base64.class
+ADDED RESOURCE aQute/lib/base64/packageinfo
+ADDED RESOURCE aQute/lib/collections/EnumerationIterator.class
+ADDED RESOURCE aQute/lib/collections/ExtList.class
+ADDED RESOURCE aQute/lib/collections/IteratorList.class
+ADDED RESOURCE aQute/lib/collections/LineCollection.class
+ADDED RESOURCE aQute/lib/collections/Logic.class
+ADDED RESOURCE aQute/lib/collections/MultiMap$1.class
+ADDED RESOURCE aQute/lib/collections/MultiMap.class
+ADDED RESOURCE aQute/lib/collections/SortedList$1.class
+ADDED RESOURCE aQute/lib/collections/SortedList$It.class
+ADDED RESOURCE aQute/lib/collections/SortedList.class
+ADDED RESOURCE aQute/lib/collections/packageinfo
+ADDED RESOURCE aQute/lib/converter/Converter$1.class
+ADDED RESOURCE aQute/lib/converter/Converter$Hook.class
+ADDED RESOURCE aQute/lib/converter/Converter.class
+ADDED RESOURCE aQute/lib/converter/TypeReference.class
+ADDED RESOURCE aQute/lib/converter/packageinfo
+ADDED RESOURCE aQute/lib/deployer/FileRepo$1.class
+ADDED RESOURCE aQute/lib/deployer/FileRepo$2.class
+ADDED RESOURCE aQute/lib/deployer/FileRepo$3.class
+MAJOR RESOURCE aQute/lib/deployer/FileRepo.class
+ ADDED SHA 5AE445A1EFA3EA6F6BF2120F28AF029BC32DEFC1
+ REMOVED SHA DA9A1E483557668D4A3CD944C96DB236DE7920CB
+REMOVED RESOURCE aQute/lib/deployer/ProjectRepo.class
+ADDED RESOURCE aQute/lib/deployer/RDImpl.class
+REMOVED RESOURCE aQute/lib/deployer/RepoDeployer.class
+ADDED RESOURCE aQute/lib/filter/Filter$DictQuery.class
+ADDED RESOURCE aQute/lib/filter/Filter$MapQuery.class
+ADDED RESOURCE aQute/lib/filter/Filter$Query.class
+ADDED RESOURCE aQute/lib/filter/Filter.class
+ADDED RESOURCE aQute/lib/filter/packageinfo
+ADDED RESOURCE aQute/lib/getopt/Arguments.class
+ADDED RESOURCE aQute/lib/getopt/CommandLine$Option.class
+ADDED RESOURCE aQute/lib/getopt/CommandLine.class
+ADDED RESOURCE aQute/lib/getopt/CommandLineMessages.class
+ADDED RESOURCE aQute/lib/getopt/Description.class
+ADDED RESOURCE aQute/lib/getopt/OptionArgument.class
+ADDED RESOURCE aQute/lib/getopt/Options.class
+ADDED RESOURCE aQute/lib/getopt/packageinfo
+ADDED RESOURCE aQute/lib/hex/Hex.class
+ADDED RESOURCE aQute/lib/hex/packageinfo
+ADDED RESOURCE aQute/lib/io/IO$1.class
+ADDED RESOURCE aQute/lib/io/IO$2.class
+ADDED RESOURCE aQute/lib/io/IO.class
+ADDED RESOURCE aQute/lib/io/IOConstants.class
+ADDED RESOURCE aQute/lib/io/LimitedInputStream.class
+ADDED RESOURCE aQute/lib/io/packageinfo
+REMOVED RESOURCE aQute/lib/jardiff/Diff.class
+ADDED RESOURCE aQute/lib/json/ArrayHandler.class
+ADDED RESOURCE aQute/lib/json/BooleanHandler.class
+ADDED RESOURCE aQute/lib/json/ByteArrayHandler.class
+ADDED RESOURCE aQute/lib/json/CharacterHandler.class
+ADDED RESOURCE aQute/lib/json/CollectionHandler.class
+ADDED RESOURCE aQute/lib/json/DateHandler.class
+ADDED RESOURCE aQute/lib/json/Decoder.class
+ADDED RESOURCE aQute/lib/json/Encoder.class
+ADDED RESOURCE aQute/lib/json/EnumHandler.class
+ADDED RESOURCE aQute/lib/json/FileHandler.class
+ADDED RESOURCE aQute/lib/json/Handler.class
+ADDED RESOURCE aQute/lib/json/JSONCodec.class
+ADDED RESOURCE aQute/lib/json/MapHandler.class
+ADDED RESOURCE aQute/lib/json/NumberHandler.class
+ADDED RESOURCE aQute/lib/json/ObjectHandler$1.class
+ADDED RESOURCE aQute/lib/json/ObjectHandler.class
+ADDED RESOURCE aQute/lib/json/SpecialHandler.class
+ADDED RESOURCE aQute/lib/json/StringHandler.class
+ADDED RESOURCE aQute/lib/json/packageinfo
+ADDED RESOURCE aQute/lib/justif/Justif.class
+ADDED RESOURCE aQute/lib/justif/Table.class
+ADDED RESOURCE aQute/lib/justif/packageinfo
+ADDED RESOURCE aQute/lib/markdown/MarkdownFormatter.class
+REMOVED RESOURCE aQute/lib/osgi/About.class
+REMOVED RESOURCE aQute/lib/osgi/Analyzer.class
+REMOVED RESOURCE aQute/lib/osgi/Builder.class
+REMOVED RESOURCE aQute/lib/osgi/Clazz$Assoc.class
+REMOVED RESOURCE aQute/lib/osgi/Clazz$QUERY.class
+REMOVED RESOURCE aQute/lib/osgi/Clazz.class
+REMOVED RESOURCE aQute/lib/osgi/Constants.class
+REMOVED RESOURCE aQute/lib/osgi/EmbeddedResource.class
+REMOVED RESOURCE aQute/lib/osgi/FileResource.class
+REMOVED RESOURCE aQute/lib/osgi/Instruction.class
+REMOVED RESOURCE aQute/lib/osgi/Jar.class
+REMOVED RESOURCE aQute/lib/osgi/JarResource.class
+REMOVED RESOURCE aQute/lib/osgi/Macro$Link.class
+REMOVED RESOURCE aQute/lib/osgi/Macro.class
+REMOVED RESOURCE aQute/lib/osgi/OpCodes.class
+REMOVED RESOURCE aQute/lib/osgi/Plugin.class
+REMOVED RESOURCE aQute/lib/osgi/PreprocessResource.class
+REMOVED RESOURCE aQute/lib/osgi/Processor.class
+REMOVED RESOURCE aQute/lib/osgi/Resource.class
+REMOVED RESOURCE aQute/lib/osgi/URLResource.class
+REMOVED RESOURCE aQute/lib/osgi/Verifier.class
+REMOVED RESOURCE aQute/lib/osgi/ZipResource.class
+REMOVED RESOURCE aQute/lib/osgi/bnd.info
+REMOVED RESOURCE aQute/lib/osgi/eclipse/EclipseClasspath.class
+ADDED RESOURCE aQute/lib/persistentmap/PersistentMap$1$1$1.class
+ADDED RESOURCE aQute/lib/persistentmap/PersistentMap$1$1.class
+ADDED RESOURCE aQute/lib/persistentmap/PersistentMap$1.class
+ADDED RESOURCE aQute/lib/persistentmap/PersistentMap.class
+ADDED RESOURCE aQute/lib/persistentmap/packageinfo
+ADDED RESOURCE aQute/lib/settings/PasswordCryptor.class
+ADDED RESOURCE aQute/lib/settings/Settings$Data.class
+ADDED RESOURCE aQute/lib/settings/Settings.class
+ADDED RESOURCE aQute/lib/settings/packageinfo
+ADDED RESOURCE aQute/lib/strings/Strings.class
+ADDED RESOURCE aQute/lib/strings/packageinfo
+ADDED RESOURCE aQute/lib/tag/Tag.class
+ADDED RESOURCE aQute/lib/tag/packageinfo
+ADDED RESOURCE aQute/libg/classdump/ClassDumper$Assoc.class
+ADDED RESOURCE aQute/libg/classdump/ClassDumper.class
+ADDED RESOURCE aQute/libg/classdump/packageinfo
+ADDED RESOURCE aQute/libg/command/Command$1.class
+ADDED RESOURCE aQute/libg/command/Command$2.class
+ADDED RESOURCE aQute/libg/command/Command$3.class
+ADDED RESOURCE aQute/libg/command/Command$Collector.class
+ADDED RESOURCE aQute/libg/command/Command.class
+ADDED RESOURCE aQute/libg/command/packageinfo
+ADDED RESOURCE aQute/libg/cryptography/Crypto.class
+ADDED RESOURCE aQute/libg/cryptography/Digest.class
+ADDED RESOURCE aQute/libg/cryptography/Digester.class
+ADDED RESOURCE aQute/libg/cryptography/Key.class
+ADDED RESOURCE aQute/libg/cryptography/MD5$1.class
+ADDED RESOURCE aQute/libg/cryptography/MD5.class
+ADDED RESOURCE aQute/libg/cryptography/RSA.class
+ADDED RESOURCE aQute/libg/cryptography/SHA1$1.class
+ADDED RESOURCE aQute/libg/cryptography/SHA1.class
+ADDED RESOURCE aQute/libg/cryptography/SHA256$1.class
+ADDED RESOURCE aQute/libg/cryptography/SHA256.class
+ADDED RESOURCE aQute/libg/cryptography/Signer.class
+ADDED RESOURCE aQute/libg/cryptography/Verifier.class
+ADDED RESOURCE aQute/libg/cryptography/packageinfo
+ADDED RESOURCE aQute/libg/filelock/DirectoryLock.class
+ADDED RESOURCE aQute/libg/filelock/packageinfo
+ADDED RESOURCE aQute/libg/filters/AndFilter.class
+ADDED RESOURCE aQute/libg/filters/Filter.class
+ADDED RESOURCE aQute/libg/filters/LiteralFilter.class
+ADDED RESOURCE aQute/libg/filters/NotFilter.class
+ADDED RESOURCE aQute/libg/filters/Operator.class
+ADDED RESOURCE aQute/libg/filters/OrFilter.class
+ADDED RESOURCE aQute/libg/filters/SimpleFilter.class
+ADDED RESOURCE aQute/libg/filters/packageinfo
+ADDED RESOURCE aQute/libg/forker/Forker$Job.class
+ADDED RESOURCE aQute/libg/forker/Forker.class
+ADDED RESOURCE aQute/libg/forker/packageinfo
+ADDED RESOURCE aQute/libg/generics/Create.class
+ADDED RESOURCE aQute/libg/generics/packageinfo
+ADDED RESOURCE aQute/libg/glob/Glob.class
+ADDED RESOURCE aQute/libg/glob/packageinfo
+ADDED RESOURCE aQute/libg/map/MAP$MAPX.class
+ADDED RESOURCE aQute/libg/map/MAP.class
+ADDED RESOURCE aQute/libg/map/packageinfo
+ADDED RESOURCE aQute/libg/qtokens/QuotedTokenizer.class
+ADDED RESOURCE aQute/libg/qtokens/packageinfo
+ADDED RESOURCE aQute/libg/reporter/Message.class
+ADDED RESOURCE aQute/libg/reporter/ReporterAdapter$LocationImpl.class
+ADDED RESOURCE aQute/libg/reporter/ReporterAdapter.class
+ADDED RESOURCE aQute/libg/reporter/ReporterMessages$1.class
+ADDED RESOURCE aQute/libg/reporter/ReporterMessages$ERRORImpl.class
+ADDED RESOURCE aQute/libg/reporter/ReporterMessages$WARNINGImpl.class
+ADDED RESOURCE aQute/libg/reporter/ReporterMessages.class
+ADDED RESOURCE aQute/libg/reporter/packageinfo
+ADDED RESOURCE aQute/libg/sed/Domain.class
+ADDED RESOURCE aQute/libg/sed/Replacer.class
+ADDED RESOURCE aQute/libg/sed/ReplacerAdapter$1.class
+ADDED RESOURCE aQute/libg/sed/ReplacerAdapter$2.class
+ADDED RESOURCE aQute/libg/sed/ReplacerAdapter$3.class
+ADDED RESOURCE aQute/libg/sed/ReplacerAdapter$Link.class
+ADDED RESOURCE aQute/libg/sed/ReplacerAdapter.class
+ADDED RESOURCE aQute/libg/sed/Sed.class
+ADDED RESOURCE aQute/libg/sed/packageinfo
+ADDED RESOURCE aQute/libg/tuple/ComparablePair.class
+ADDED RESOURCE aQute/libg/tuple/Pair.class
+ADDED RESOURCE aQute/libg/tuple/packageinfo
+ADDED RESOURCE aQute/service/reporter/Messages$ERROR.class
+ADDED RESOURCE aQute/service/reporter/Messages$WARNING.class
+ADDED RESOURCE aQute/service/reporter/Messages.class
+ADDED RESOURCE aQute/service/reporter/Report$Location.class
+ADDED RESOURCE aQute/service/reporter/Report.class
+ADDED RESOURCE aQute/service/reporter/Reporter$SetLocation.class
+ADDED RESOURCE aQute/service/reporter/Reporter.class
+ADDED RESOURCE aQute/service/reporter/packageinfo
+ADDED RESOURCE embedded-repo.jar
+REMOVED RESOURCE maven-dependencies.txt
+ADDED RESOURCE org/osgi/resource/Capability.class
+ADDED RESOURCE org/osgi/resource/Namespace.class
+ADDED RESOURCE org/osgi/resource/Requirement.class
+ADDED RESOURCE org/osgi/resource/Resource.class
+ADDED RESOURCE org/osgi/resource/Wire.class
+ADDED RESOURCE org/osgi/resource/Wiring.class
+ADDED RESOURCE org/osgi/resource/package-info.class
+ADDED RESOURCE org/osgi/resource/packageinfo
+ADDED RESOURCE org/osgi/service/bindex/BundleIndexer.class
+ADDED RESOURCE org/osgi/service/bindex/packageinfo
+ADDED RESOURCE org/osgi/service/component/annotations/Activate.class
+ADDED RESOURCE org/osgi/service/component/annotations/Component.class
+ADDED RESOURCE org/osgi/service/component/annotations/ConfigurationPolicy.class
+ADDED RESOURCE org/osgi/service/component/annotations/Deactivate.class
+ADDED RESOURCE org/osgi/service/component/annotations/LookupReference.class
+ADDED RESOURCE org/osgi/service/component/annotations/Modified.class
+ADDED RESOURCE org/osgi/service/component/annotations/Reference.class
+ADDED RESOURCE org/osgi/service/component/annotations/ReferenceCardinality.class
+ADDED RESOURCE org/osgi/service/component/annotations/ReferencePolicy.class
+ADDED RESOURCE org/osgi/service/component/annotations/ReferencePolicyOption.class
+ADDED RESOURCE org/osgi/service/component/annotations/ReferenceScope.class
+ADDED RESOURCE org/osgi/service/component/annotations/ServiceScope.class
+ADDED RESOURCE org/osgi/service/component/annotations/package-info.class
+ADDED RESOURCE org/osgi/service/component/annotations/packageinfo
+ADDED RESOURCE org/osgi/service/coordinator/Coordination.class
+ADDED RESOURCE org/osgi/service/coordinator/CoordinationException.class
+ADDED RESOURCE org/osgi/service/coordinator/CoordinationPermission$1.class
+ADDED RESOURCE org/osgi/service/coordinator/CoordinationPermission.class
+ADDED RESOURCE org/osgi/service/coordinator/CoordinationPermissionCollection.class
+ADDED RESOURCE org/osgi/service/coordinator/Coordinator.class
+ADDED RESOURCE org/osgi/service/coordinator/Participant.class
+ADDED RESOURCE org/osgi/service/coordinator/SignerProperty.class
+ADDED RESOURCE org/osgi/service/coordinator/package-info.class
+ADDED RESOURCE org/osgi/service/coordinator/packageinfo
+ADDED RESOURCE org/osgi/service/repository/ContentNamespace.class
+ADDED RESOURCE org/osgi/service/repository/Repository.class
+ADDED RESOURCE org/osgi/service/repository/RepositoryContent.class
+ADDED RESOURCE org/osgi/service/repository/package-info.class
+ADDED RESOURCE org/osgi/service/repository/packageinfo
+REMOVED RESOURCE plugin.xml
+REMOVED RESOURCE pom.xml
+ADDED RESOURCE templates/enroute.zip
+
Digest a number of files
+ +[ -a, --algorithm <alg>* ] - Specify the algorithms
+[ -b, --b64 ] - Show base64 output
+[ -h, --hex ] - Show hex output (default)
+[ -n, --name ] - Show the file name
+[ -p, --process ] - Show process info
+
biz.aQute.bnd (master)$ bnd digest generated/biz.aQute.bnd.jar
+16B415286B53FA499BD7B2684A93924CA7C198C8
+
Execute a file based on its extension. Supported extensions are bnd (build), bndrun (run), and jar (print)
+ +[ -f, --force ] - Force even when there are errors
+[ -o, --output <string> ] - The output file
+
biz.aQute.bnd (master)$ bnd do generated/biz.aQute.bnd.jar
+[MANIFEST biz.aQute.bnd]
+Bnd-LastModified 1404915822703
+Bundle-Copyright Copyright (c) aQute (2000, 2014). All Rights Reserved.
+Bundle-Description This command line utility is the Swiss army knife of OSGi. It provides you with a breadth
+Bundle-DocURL http://www.aQute.biz/Code/Bnd
+Bundle-License http://www.opensource.org/licenses/apache2.0.php; description="Apache License, Version 2.0"; link=http://www.apache.org/licenses/LICENSE-2.0.html
+Bundle-ManifestVersion 2
+Bundle-Name biz.aQute.bnd
+Bundle-SCM git://github.com/bndtools/bnd.git
+Bundle-SymbolicName biz.aQute.bnd
+Bundle-Vendor aQute SARL http://www.aQute.biz
+Bundle-Version 2.4.0.201407091423
+Conditional-Package aQute.libg.*,aQute.lib.*,aQute.configurable
+Created-By 1.8.0 (Oracle Corporation)
+Export-Package aQute.bnd.service;version="4.1.0";uses:="aQute.bnd.build,aQute.bnd.osgi,aQute.bnd.version,aQute.service.reporter",aQute.bnd.service.action;version="2.0.0";uses:="aQute.bnd.build",aQute.bnd.service.classparser;version="1.0";uses:="aQute.bnd.osgi",aQute.bnd.service.diff;version="1.0";uses:="aQute.bnd.osgi",aQute.bnd.service.extension;version="1.0";uses:="aQute.bnd.build",aQute.bnd.service.progress;version="1.0.0",aQute.bnd.service.repository;version="1.2";uses:="aQute.bnd.service,aQute.bnd.version,aQute.service.reporter,org.osgi.resource",aQute.bnd.service.resolve.hook;version="1.0";uses:="org.osgi.resource",aQute.bnd.service.url;version="1.2",aQute.bnd.header;version="1.3.0";uses:="aQute.bnd.version,aQute.service.reporter",aQute.bnd.osgi;version="2.3.0";uses:="aQute.bnd.build,aQute.bnd.header,aQute.bnd.service,aQute.bnd.util.dto,aQute.bnd.version,aQute.service.reporter",aQute.bnd.build;version="2.4.0";uses:="aQute.bnd.maven.support,aQute.bnd.osgi,aQute.bnd.service,aQute.bnd.service.action,aQute.bnd.version,aQute.service.reporter",aQute.bnd.version;version="1.1.0",aQute.bnd.maven.support;version="2.0";uses:="aQute.bnd.service,aQute.bnd.version,aQute.service.reporter,javax.xml.xpath,org.w3c.dom",org.osgi.service.bindex;version="1.0",aQute.service.reporter;version="1.0.1",aQute.bnd.osgi.resource;version="1.4.0";uses:="aQute.bnd.header,aQute.bnd.util.dto,org.osgi.resource",org.osgi.service.repository;version="1.0";uses:="org.osgi.resource",org.osgi.resource;version="1.0",aQute.bnd.util.dto;version="1.0"
+Git-Descriptor 2.4.0.M1-66-gc1ad07d-dirty
+Git-SHA c1ad07dfeb4704ce590bd93c1405d7bfe8bef131
+Import-Package org.apache.tools.ant;resolution:=optional,org.apache.tools.ant.taskdefs;resolution:=optional,org.apache.tools.ant.types;resolution:=optional,aQute.bnd.service;version="[4.1,5)",aQute.bnd.service.action;version="[2.0,2.1)",aQute.bnd.service.diff;version="[1.0,2)",aQute.bnd.service.progress;version="[1.0,2)",aQute.bnd.service.repository;version="[1.2,2)",aQute.bnd.service.url;version="[1.2,2)",aQute.bnd.version;version="[1.1,2)",aQute.service.reporter;version="[1.0,2)",javax.crypto,javax.crypto.spec,javax.naming,javax.net.ssl,javax.script,javax.xml.namespace,javax.xml.parsers,javax.xml.transform,javax.xml.transform.dom,javax.xml.transform.stream,javax.xml.xpath,org.osgi.framework;version="[1.6,2)",org.osgi.resource;version="[1.0,2)",org.osgi.service.log;version="[1.3,2)",org.w3c.dom,org.xml.sax,org.xml.sax.helpers,junit.framework;resolution:=optional;version="[3.8,4)"
+Main-Class aQute.bnd.main.bnd
+Manifest-Version 1.0
+Private-Package aQute.bnd.annotation;version="1.43.2",aQute.bnd.annotation.component;version="1.43.1",aQute.bnd.annotation.headers;version="1.0",aQute.bnd.annotation.licenses;version="1.0",aQute.bnd.annotation.metatype;version="1.44.1",aQute.bnd.ant,aQute.bnd.build.model;version="2.6.0",aQute.bnd.build.model.clauses;version=2,aQute.bnd.build.model.conversions,aQute.bnd.compatibility,aQute.bnd.component,aQute.bnd.component.error;version="1.0.0",aQute.bnd.differ;version="1.1.0",aQute.bnd.enroute.commands,aQute.bnd.filerepo;version="1.0",aQute.bnd.gradle,aQute.bnd.help;version="1.1",aQute.bnd.indexer,aQute.bnd.indexer.analyzers,aQute.bnd.main;version="0.9",aQute.bnd.make,aQute.bnd.make.calltree,aQute.bnd.make.component,aQute.bnd.make.coverage,aQute.bnd.make.metatype,aQute.bnd.maven,aQute.bnd.obr,aQute.bnd.osgi.eclipse,aQute.bnd.properties;version="2.0",aQute.bnd.resource.repository,aQute.bnd.signing,aQute.bnd.testing;version="1.0",aQute.bnd.url;version="1.0",aQute.configurable;version="1.0.0",aQute.lib.deployer,embedded-repo.jar,org.osgi.service.component.annotations;version="1.3",org.osgi.service.coordinator;version="1.0",templates,aQute.lib.base64;version="1.2.0",aQute.lib.collections;version="1.2.0",aQute.lib.converter;version="2.0.1",aQute.lib.filter;version="1.1.0",aQute.lib.getopt;version="1.0.0",aQute.lib.hex;version="1.1.0",aQute.lib.io;version="1.4.0",aQute.lib.json;version="3.0.0",aQute.lib.justif;version="1.1.0",aQute.lib.persistentmap;version="1.1.0",aQute.lib.settings;version="1.2.0",aQute.lib.strings;version="1.1.0",aQute.lib.tag;version="1.1",aQute.libg.classdump;version="1.0",aQute.libg.command;version="3.0.0",aQute.libg.cryptography;version="1.1.0",aQute.libg.filelock;version="1.0.0",aQute.libg.filters;version="1.0",aQute.libg.forker;version="1.0",aQute.libg.generics;version="1.0",aQute.libg.glob;version="1.1.1",aQute.libg.map;version="1.2.0",aQute.libg.qtokens;version="1.0",aQute.libg.reporter;version="1.5",aQute.libg.sed;version="1.1.0",aQute.libg.tuple;version="1.0",aQute.lib.markdown
+Require-Capability osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.6))"
+Tool Bnd-2.4.1.201406261752
+
THIS IS WORK IN PROGRESS
+ +Import PDE projects into a bnd workspace
+ +The control file (-i) has the following properties:
+ +pde.selection = \
+ com.example.extension.diagnostic, \
+ com.example.security.foo.*; \
+ com.example.osgi.*; \
+ -workingset=sample, \
+ com.example.bar; \
+ -buildpath="org.apache.commons.lang,com.google.gson;version=3", \
+
The structure is a normal OSGi Parameters, where they keys of clauses can be connected. Keys can be globbed. This will match the directory of a directory that is a PDE project as signalled by a build.properties file. Notice that you can specify multiple directory globs for one clause.
+ +The attributes/directives of a clause provide information that is added to the bnd file before analysis.
+ +Although this format is quite powerful it is very error prone. Check your commas and semi-colons, especially when multiple entries on the -buildpath or -testpath are specified.
+ +The pde command will process the PDE repository directories and scan for PDE projects. A PDE project is a directory that has a build.properties
file. It will then match the directory name against one of the keys in the pde.selection
property of the control file. If this matches, it will convert the project.
It will first parse the manifest to establish the bundle symbolic name. It will then create a bnd project in the provided workspace with the symbolic name. It will use the workspace settings for the workspace as defined by the following properties:
+ +src: source directory, e.g. src/main/java
+src.resources: resources directory, e.g. src/main/resources
+bin: output directory main, e.g. target/classes
+testsrc: test source (singular!), e.g. src/test/java
+testsrc.resources: test resources, e.g. src/test/resources
+testbin: test outout, e.g. target/test-classes
+target-dir: general write directory, e.g. target
+
It will use the information in the build.properties file to copy these different aspects of a PDE project to the place in a bnd project. If the control file specifies attributes for the given project, then they are added to the bnd.bnd file. The bnd file will be fully setup for Eclipse with a proper .classpath and .project files. Any natures and build command other than bnd, e.g. Groovy, are copied.
+ +After all selected PDE projects are converted, bnd will analyze the newly created projects. It will scan the source code and detect most dependencies. It will then try to find an exporter in either another bnd project in the same workspace or through one of the workspace’s repositories. This is done seperately for both the main aspect of the project (-buildpath) as well as the test aspect (-testpath).
+ +Although it is a very decent start, this analysis is not perfect
+ +-w, --workspace <dir> - The bnd workspace directory
+-c, --clean - Clean the project directory before converting
+-s, --set - Set Eclipse workingset
+-i, --instructions <file.bnd>* - Control file what to convert and augment
+-r, --recurse - Show references to other classes/packages (>)
+
bnd eclipse pde
+ -c
+ -w com.example.bnd.workspace
+ -s example.workingset
+ -i control.bnd
+ -r git_repo
+
Show info about the current directory’s eclipse project
+ +[ -d, --dir <string> ] - Path to the project
+
$ bnd eclipse
+Classpath [/Ws/bnd/biz.aQute.bnd/bin]
+Dependents []
+Sourcepath [/Ws/bnd/biz.aQute.bnd/src, /Ws/bnd/biz.aQute.bnd/test]
+Output /Ws/bnd/biz.aQute.bnd/bin
+
Show the Execution Environments of a JAR
+ +Generate and export reports of a workspace, a project or of a Jar.
+ +Custom reports must first be configured in the project or the workspace with the -exportreport intruction and optionaly with the -reportconfig intruction. For an “external” Jar the reports can be configured directly with the command line (replacing the -exportreport
instruction), however if you need to fine tune the report the -reportconfig
has to be in a properties file.
For a general introduction of the feature you can look at the here.
+ +exportreport <[sub-cmd]> [options]
+
Available sub-commands:
+
+ list - List the user defined reports.
+ export - Export the user defined reports.
+ jarexport - Export a custom report of a Jar.
+ readme - Export a set of readme files.
+ jarreadme - Export a readme file of a Jar
+
List the user defined reports absolute path which could be exported by the workpace and/or the projects. If this command is applied on a workspace, the command will also list reports of all the projects (except if you exclude them).
+ +list [options]
+
[ -e, --exclude <string;> ] - Exclude files by pattern
+[ -p, --project <string> ] - Identify another project
+[ -v, --verbose ] - Prints more processing information
+[ -w, --workspace <string> ] - Use the following workspace
+
Generate and export the user defined reports. If this command is applied on a workspace, the command will also export reports of all the projects (except if you exclude them).
+ +export [options]
+
[ -e, --exclude <string;> ] - Exclude files by pattern
+[ -p, --project <string> ] - Identify another project
+[ -v, --verbose ] - Prints more processing information
+[ -w, --workspace <string> ] - Use the following workspace
+
Generate and export a custom report of a Jar.
+ +jarexport [options] <jar path> <output path>
+
[ -c, --configName <string> ] - A configuration name defined in the property
+ file (check -reportconfig documentation), if not
+ set a default configuration will be used.
+[ -l, --locale <string> ] - A locale (language-COUNTRY-variant) used to
+ localized the report data.
+[ -p, --parameters <string;> ] - A list of parameters that will be provided
+ to the transformation process if any.
+[ -P, --properties <string> ] - Path to a property file
+[ -t, --template <string> ] - Path or URL to a template file used to
+ transform the generated report (twig or xslt).
+[ -T, --templateType <string> ] - The template type (aka template file
+ extension), must be set if it could not be guess
+ from the template file name.
+
Create an XML
report of a Jar:
$ bnd exportreport jarexport ./m2/.../my.bundle.jar ./my-report.xml
+
Create a JSON
report of a Jar:
$ bnd exportreport jarexport ./m2/.../my.bundle.jar ./my-report.json
+
Generate a web page from a Jar. Here, we specify the template type because the URL is ambiguous and a locale to get data in French:
+ +$ bnd exportreport jarexport --locale fr-FR --template https://..../templates/56z5f --templateType xslt ./m2/.../my.bundle.jar ./webpage.html
+
Generate and export a set of readme files. If this command is applied on a workspace, the command will also export readme files for each projects (except if you exclude them).
+ +readme [options]
+
[ -e, --exclude <string;> ] - Exclude files by pattern
+[ -p, --project <string> ] - Identify another project
+[ -v, --verbose ] - Prints more processing information
+[ -w, --workspace <string> ] - Use the following workspace
+
++ +Template can be parametrized with system properties starting with ‘bnd.reporter.*’. See +here for a complete list of parameters.
+
Generate and export a readme file of a Jar.
+ +jarreadme <jar path> <output path>
+
++ +Template can be parametrized with system properties starting with ‘bnd.reporter.*’. See +here for a complete list of parameters.
+
Extract files from a JAR file, equivalent jar command x[vf] (syntax supported)
+ +[ -c, --cdir <string> ] - Directory where to store
+[ -f, --file <string> ] - Jar file (f option)
+[ -v, --verbose ] - Verbose (v option)
+
biz.aQute.bnd (master)$ bnd extract -c generated/tmp generated/biz.aQute.bnd.jar
+
Go through the exports and/or imports and match the given exports/imports globs. If they match, print the file, package and version.
+ +[ -e, --exports <glob;> ] - Glob expression on the exports.
+[ -i, --imports <glob;> ] - Glob expression on the imports.
+
biz.aQute.bnd (master)$ bnd find -e *service* generated/*.jar
+>/Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar: aQute.bnd.service-4.1.0
+>/Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar: aQute.bnd.service.action-2.0.0
+>/Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar: aQute.bnd.service.classparser-1.0
+>/Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar: aQute.bnd.service.diff-1.0
+>/Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar: aQute.bnd.service.extension-1.0
+>/Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar: aQute.bnd.service.progress-1.0.0
+>/Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar: aQute.bnd.service.repository-1.2
+>/Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar: aQute.bnd.service.resolve.hook-1.0
+>/Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar: aQute.bnd.service.url-1.2
+>/Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar: org.osgi.service.bindex-1.0
+>/Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar: aQute.service.reporter-1.0.1
+>/Ws/bnd/biz.aQute.bnd/generated/biz.aQute.bnd.jar: org.osgi.service.repository-1.0
+
Generate autocompletion file for bash
+ +generate
+ +Grep the manifest of bundles/jar files.
+ +[ -b, --bsn ] - Search in bsn
+[ -e, --exports ] - Search in exports
+[ -h, --headers <string>* ] - Set header(s) to search, can be wildcarded. The
+ default is all headers (*).
+[ -i, --imports ] - Search in imports
+
biz.aQute.bnd (master)$ bnd grep -h "*" "settings" generated/*.jar
+ generated/biz.aQute.bnd.jar : Private-Package ...ute.lib.[settings]...
+
identity
+ +Index bundles from the local file system
+ +[ -b, --base <uri> ] - URI from which to make paths in the index file
+ relative (default: relative to the output file
+ directory). The specified value must be a prefix
+ of the absolute output file directory in order
+ to have any effect
+[ -d, --directory <file> ] - The directory to write the repository index file
+ (default: the current directory)
+[ -n, --name <string> ] - The name of the index (default: name of the
+ output file directory)
+[ -r, --repositoryIndex <file> ] - The name of the repository index file
+ (default: 'index.xml'). To enable GZIP
+ compression use the file extension '.gz' (e.g.
+ 'index.xml.gz')
+
Show key project variables
+ +[ -b, --buildpath ] -
+[ -c, --classpath ] -
+[ -d, --dependsOn ] -
+[ -p, --project <string> ] -
+[ -r, --runbundles ] -
+[ -s, --sourcepath ] -
+[ -v, --vmpath ] -
+
biz.aQute.bnd (master)$ bnd info -b
+found password
+Build [/Ws/bnd/biz.aQute.bnd/bin, /Ws/bnd/aQute.libg/generated/aQute.libg.jar, /Ws/bnd/biz.aQute.bndlib/bin, /Ws/bnd/cnf/repo/org.apache.tools.ant/org.apache.tools.ant-1.6.5.jar, /Ws/bnd/cnf/repo/org.osgi.service.component.annotations/org.osgi.service.component.annotations-6.0.0.jar, /Ws/bnd/cnf/repo/osgi.cmpn/osgi.cmpn-4.3.1.jar, /Ws/bnd/cnf/repo/osgi.core/osgi.core-4.3.1.jar, /Ws/bnd/cnf/repo/org.osgi.impl.bundle.bindex/org.osgi.impl.bundle.bindex-2.2.0.jar, /Ws/bnd/cnf/repo/osgi.r5/osgi.r5-1.0.1.jar]
+
+Class path []
+
+Depends on [aQute.libg, biz.aQute.bndlib, biz.aQute.junit, biz.aQute.launcher]
+
+Run []
+
+Run path [/Ws/bnd/cnf/repo/org.eclipse.osgi/org.eclipse.osgi-3.6.0.jar, /Ws/bnd/cnf/repo/com.springsource.junit/com.springsource.junit-3.8.2.jar]
+
+Source [/Ws/bnd/biz.aQute.bnd/src]
+
+biz.aQute.bnd (master)$ bnd info -bcdrsv
+found password
+Build [/Ws/bnd/biz.aQute.bnd/bin, /Ws/bnd/aQute.libg/generated/aQute.libg.jar, /Ws/bnd/biz.aQute.bndlib/bin, /Ws/bnd/cnf/repo/org.apache.tools.ant/org.apache.tools.ant-1.6.5.jar, /Ws/bnd/cnf/repo/org.osgi.service.component.annotations/org.osgi.service.component.annotations-6.0.0.jar, /Ws/bnd/cnf/repo/osgi.cmpn/osgi.cmpn-4.3.1.jar, /Ws/bnd/cnf/repo/osgi.core/osgi.core-4.3.1.jar, /Ws/bnd/cnf/repo/org.osgi.impl.bundle.bindex/org.osgi.impl.bundle.bindex-2.2.0.jar, /Ws/bnd/cnf/repo/osgi.r5/osgi.r5-1.0.1.jar]
+
+Class path []
+
+Depends on [aQute.libg, biz.aQute.bndlib, biz.aQute.junit, biz.aQute.launcher]
+
+Run []
+
+Run path [/Ws/bnd/cnf/repo/org.eclipse.osgi/org.eclipse.osgi-3.6.0.jar, /Ws/bnd/cnf/repo/com.springsource.junit/com.springsource.junit-3.8.2.jar]
+
+Source [/Ws/bnd/biz.aQute.bnd/src]
+
TODO Does not work yet
+ +Test a project according to an OSGi test
+ +[ -c, --continuous ] - Set the -testcontinuous flag
+[ -f, --force ] - Launch the test even if this bundle does not
+ contain Test-Cases
+[ -p, --project <string> ] - Path to another project than the current project
+[ -t, --trace ] - Set the -runtrace flag
+[ -v, --verify ] - Verify all the dependencies before launching
+ (runpath, runbundles, testpath)
+
Show macro value. Macro can contain the { and } parentheses but it is also ok without. You can use the ‘:’ instead of the ‘;’ in a macro
+ +[ -p, --project <string> ] - Path to project, default current directory
+
biz.aQute.bnd (master)$ bnd macro p
+biz.aQute.bnd
+biz.aQute.bnd (master)$
+
Special maven commands
+ +maven
+[-temp <dir>] use as temp directory
+settings show maven settings
+bundle turn a bundle into a maven bundle
+ [-properties <file>] provide properties, properties starting with javadoc are options for javadoc, like javadoc-tag=...
+ [-javadoc <file|url>] where to find the javadoc (zip/dir), otherwise generated
+ [-source <file|url>] where to find the source (zip/dir), otherwise from OSGI-OPT/src
+ [-scm <url>] required scm in pom, otherwise from Bundle-SCM
+ [-url <url>] required project url in pom
+ [-bsn bsn] overrides bsn
+ [-version <version>] overrides version
+ [-developer <email>] developer email
+ [-nodelete] do not delete temp files
+ [-passphrase <gpgp passphrase>] signer password
+ <file|url>
+
biz.aQute.bnd (master)$ bnd maven settings
+<?xml version="1.0" encoding="UTF-8"?>
+<settings xmlns="http://maven.apache.org/settings/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
+ <!-- <localRepository>C:/Users/franklan/.m2/repository</localRepository> -->
+ <pluginGroups>
+ <!-- pluginGroup
+ | Specifies a further group identifier to use for plugin lookup.
+ <pluginGroup>com.your.plugins</pluginGroup>
+ -->
+ </pluginGroups>
+ <servers>
+ <server>
+ <id>deploymentRepo</id>
+ <username>deployment</username>
+ <password>deployment123</password>
+ <!-- <privateKey>c://config//Frank_Langel.p12</privateKey> -->
+ </server>
+ </servers>
+ <mirrors>
+ <mirror>
+ <id>nexus</id>
+ <mirrorOf>*</mirrorOf>
+ <url>https://svn.myfarm365.de/nexus/content/groups/public</url>
+ <privateKey>/Users/aqute/Desktop/Peter_Kriens.p12</privateKey>
+ </mirror>
+ </mirrors>
+ <profiles>
+ <profile>
+ <id>nexus</id>
+ <!--Enable snapshots for the built in central repo to direct -->
+ <!--all requests to nexus via the mirror -->
+ <repositories>
+ <repository>
+ <id>central</id>
+ <url>http://central</url> <!-- URL wird nicht verwendet, da mirror alles zu nexus weiterleitet -->
+ <releases>
+ <enabled>true</enabled>
+ <updatePolicy>always</updatePolicy>
+ </releases>
+ <snapshots>
+ <enabled>true</enabled>
+ <updatePolicy>always</updatePolicy>
+ </snapshots>
+ </repository>
+ </repositories>
+ <pluginRepositories>
+ <pluginRepository>
+ <url>http://central</url> <!-- URL wird nicht verwendet, da mirror alles zu nexus weiterleitet -->
+ <id>central</id>
+ <releases>
+ <enabled>true</enabled>
+ <updatePolicy>always</updatePolicy>
+ </releases>
+ <snapshots>
+ <enabled>true</enabled>
+ <updatePolicy>always</updatePolicy>
+ </snapshots>
+ </pluginRepository>
+ </pluginRepositories>
+ </profile>
+
+ </profiles>
+ <activeProfiles>
+ <activeProfile>nexus</activeProfile>
+ </activeProfiles>
+</settings>
+
Package a bnd or bndrun file into a single jar that executes with java -jar <>.jar. The JAR contains all dependencies, including the framework and the launcher. A profile can be specified which will be used to find properties. If a property is not found, a property with the name [
[ -o, --output <string> ] - Where to store the resulting file. Default the
+ name of the bnd file with a .jar extension.
+[ -p, --profile <string> ] - Profile name. Default no profile
+
Execute a Project action, or if no parms given, show information about the project
+ +[ -p, --project <string> ] - Identify another project
+
biz.aQute.bnd (master)$ bnd plugins
+found password
+000 aQute.bnd.build.Workspace
+001 java.util.concurrent.ThreadPoolExecutor@2685c106[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
+002 java.util.Random@174384ac
+003 aQute.bnd.maven.support.Maven@51bb4422
+004 aQute.lib.settings.Settings@5d763e19
+005 bnd-cache
+006 aQute.bnd.resource.repository.ResourceRepositoryImpl@67528259
+007 aQute.bnd.osgi.Processor
+008 /Ws/bnd/cnf/repo r/w=true
+009 /Ws/bnd/dist/bundles r/w=true
+010 aQute.bnd.signing.JartoolSigner@2dac2cb7
+
Provides detailed view of the bundle. It will analyze the bundle and then show its contents from different perspectives. If no options are specified, prints the manifest.
+ +[ -all ] - Do all (this is the default)
+[ -a, --api ] - Print the api usage. This shows the usage
+ constraints on exported packages when only
+ public API is used.
+[ -b, --by ] - Transposed uses. Will show for each known
+ package who it is used by.
+[ -c, --component ] - Show components in detail
+[ -i, --impexp ] - List the imports exports, versions and ranges
+[ -j, --java ] - Keep references to java in --api, --uses, and
+ --usedby.
+[ -l, --list ] - List the resources
+[ -m, --manifest ] - Print the manifest.
+[ -t, --typemeta ] - Show any metatype data
+[ -u, --uses ] - Show for each contained package, what other
+ package it uses. Is either an private, exported,
+ or imported package
+[ -v, --verify ] - Before printing, verify that the bundle is
+ correct.
+[ -x, --xport ] - Show all packages, not just exported, in the API
+ view
+
biz.aQute.bnd (master)$ bnd print generated/biz.aQute.bnd.jar
+[MANIFEST biz.aQute.bnd]
+Bnd-LastModified 1404918434023
+Bundle-Copyright Copyright (c) aQute (2000, 2014). All Rights Reserved.
+Bundle-Description This command line utility is the Swiss army knife of OSGi. It provides you with a breadth
+Bundle-DocURL http://www.aQute.biz/Code/Bnd
+Bundle-License http://www.opensource.org/licenses/apache2.0.php; description="Apache License, Version 2.0"; link=http://www.apache.org/licenses/LICENSE-2.0.html
+Bundle-ManifestVersion 2
+Bundle-Name biz.aQute.bnd
+Bundle-SCM git://github.com/bndtools/bnd.git
+Bundle-SymbolicName biz.aQute.bnd
+Bundle-Vendor aQute SARL http://www.aQute.biz
+Bundle-Version 2.4.0.201407091507
+Conditional-Package aQute.libg.*,aQute.lib.*,aQute.configurable
+Created-By 1.8.0 (Oracle Corporation)
+Export-Package aQute.bnd.service;version="4.1.0";uses:="aQute.bnd.build,aQute.bnd.osgi,aQute.bnd.version,aQute.service.reporter",aQute.bnd.service.action;version="2.0.0";uses:="aQute.bnd.build",aQute.bnd.service.classparser;version="1.0";uses:="aQute.bnd.osgi",aQute.bnd.service.diff;version="1.0";uses:="aQute.bnd.osgi",aQute.bnd.service.extension;version="1.0";uses:="aQute.bnd.build",aQute.bnd.service.progress;version="1.0.0",aQute.bnd.service.repository;version="1.2";uses:="aQute.bnd.service,aQute.bnd.version,aQute.service.reporter,org.osgi.resource",aQute.bnd.service.resolve.hook;version="1.0";uses:="org.osgi.resource",aQute.bnd.service.url;version="1.2",aQute.bnd.header;version="1.3.0";uses:="aQute.bnd.version,aQute.service.reporter",aQute.bnd.osgi;version="2.3.0";uses:="aQute.bnd.build,aQute.bnd.header,aQute.bnd.service,aQute.bnd.util.dto,aQute.bnd.version,aQute.service.reporter",aQute.bnd.build;version="2.4.0";uses:="aQute.bnd.maven.support,aQute.bnd.osgi,aQute.bnd.service,aQute.bnd.service.action,aQute.bnd.version,aQute.service.reporter",aQute.bnd.version;version="1.1.0",aQute.bnd.maven.support;version="2.0";uses:="aQute.bnd.service,aQute.bnd.version,aQute.service.reporter,javax.xml.xpath,org.w3c.dom",org.osgi.service.bindex;version="1.0",aQute.service.reporter;version="1.0.1",aQute.bnd.osgi.resource;version="1.4.0";uses:="aQute.bnd.header,aQute.bnd.util.dto,org.osgi.resource",org.osgi.service.repository;version="1.0";uses:="org.osgi.resource",org.osgi.resource;version="1.0",aQute.bnd.util.dto;version="1.0"
+Git-Descriptor 2.4.0.M1-66-gc1ad07d-dirty
+Git-SHA c1ad07dfeb4704ce590bd93c1405d7bfe8bef131
+Import-Package org.apache.tools.ant;resolution:=optional,org.apache.tools.ant.taskdefs;resolution:=optional,org.apache.tools.ant.types;resolution:=optional,aQute.bnd.service;version="[4.1,5)",aQute.bnd.service.action;version="[2.0,2.1)",aQute.bnd.service.diff;version="[1.0,2)",aQute.bnd.service.progress;version="[1.0,2)",aQute.bnd.service.repository;version="[1.2,2)",aQute.bnd.service.url;version="[1.2,2)",aQute.bnd.version;version="[1.1,2)",aQute.service.reporter;version="[1.0,2)",javax.crypto,javax.crypto.spec,javax.naming,javax.net.ssl,javax.script,javax.xml.namespace,javax.xml.parsers,javax.xml.transform,javax.xml.transform.dom,javax.xml.transform.stream,javax.xml.xpath,org.osgi.framework;version="[1.6,2)",org.osgi.resource;version="[1.0,2)",org.osgi.service.log;version="[1.3,2)",org.w3c.dom,org.xml.sax,org.xml.sax.helpers,junit.framework;resolution:=optional;version="[3.8,4)"
+Main-Class aQute.bnd.main.bnd
+Manifest-Version 1.0
+Private-Package aQute.bnd.annotation;version="1.43.2",aQute.bnd.annotation.component;version="1.43.1",aQute.bnd.annotation.headers;version="1.0",aQute.bnd.annotation.licenses;version="1.0",aQute.bnd.annotation.metatype;version="1.44.1",aQute.bnd.ant,aQute.bnd.build.model;version="2.6.0",aQute.bnd.build.model.clauses;version=2,aQute.bnd.build.model.conversions,aQute.bnd.compatibility,aQute.bnd.component,aQute.bnd.component.error;version="1.0.0",aQute.bnd.differ;version="1.1.0",aQute.bnd.enroute.commands,aQute.bnd.filerepo;version="1.0",aQute.bnd.gradle,aQute.bnd.help;version="1.1",aQute.bnd.indexer,aQute.bnd.indexer.analyzers,aQute.bnd.main;version="0.9",aQute.bnd.make,aQute.bnd.make.calltree,aQute.bnd.make.component,aQute.bnd.make.coverage,aQute.bnd.make.metatype,aQute.bnd.maven,aQute.bnd.obr,aQute.bnd.osgi.eclipse,aQute.bnd.properties;version="2.0",aQute.bnd.resource.repository,aQute.bnd.signing,aQute.bnd.testing;version="1.0",aQute.bnd.url;version="1.0",aQute.configurable;version="1.0.0",aQute.lib.deployer,embedded-repo.jar,org.osgi.service.component.annotations;version="1.3",org.osgi.service.coordinator;version="1.0",templates,aQute.lib.base64;version="1.2.0",aQute.lib.collections;version="1.2.0",aQute.lib.converter;version="2.0.1",aQute.lib.filter;version="1.1.0",aQute.lib.getopt;version="1.0.0",aQute.lib.hex;version="1.1.0",aQute.lib.io;version="1.4.0",aQute.lib.json;version="3.0.0",aQute.lib.justif;version="1.1.0",aQute.lib.persistentmap;version="1.1.0",aQute.lib.settings;version="1.2.0",aQute.lib.strings;version="1.1.0",aQute.lib.tag;version="1.1",aQute.libg.classdump;version="1.0",aQute.libg.command;version="3.0.0",aQute.libg.cryptography;version="1.1.0",aQute.libg.filelock;version="1.0.0",aQute.libg.filters;version="1.0",aQute.libg.forker;version="1.0",aQute.libg.generics;version="1.0",aQute.libg.glob;version="1.1.1",aQute.libg.map;version="1.2.0",aQute.libg.qtokens;version="1.0",aQute.libg.reporter;version="1.5",aQute.libg.sed;version="1.1.0",aQute.libg.tuple;version="1.0",aQute.lib.markdown
+Require-Capability osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.6))"
+Tool Bnd-2.4.1.201406261752
+
+[IMPEXP]
+Import-Package
+ aQute.bnd.service {version=[4.1,5)}
+ aQute.bnd.service.action {version=[2.0,2.1)}
+ aQute.bnd.service.diff {version=[1.0,2)}
+ aQute.bnd.service.progress {version=[1.0,2)}
+ aQute.bnd.service.repository {version=[1.2,2)}
+ aQute.bnd.service.url {version=[1.2,2)}
+ aQute.bnd.version {version=[1.1,2)}
+ aQute.service.reporter {version=[1.0,2)}
+ javax.crypto
+ javax.crypto.spec
+ javax.naming
+ javax.net.ssl
+ javax.script
+ javax.xml.namespace
+ javax.xml.parsers
+ javax.xml.transform
+ javax.xml.transform.dom
+ javax.xml.transform.stream
+ javax.xml.xpath
+ junit.framework {resolution:=optional, version=[3.8,4)}
+ org.apache.tools.ant {resolution:=optional}
+ org.apache.tools.ant.taskdefs {resolution:=optional}
+ org.apache.tools.ant.types {resolution:=optional}
+ org.osgi.framework {version=[1.6,2)}
+ org.osgi.resource {version=[1.0,2)}
+ org.osgi.service.log {version=[1.3,2)}
+ org.w3c.dom
+ org.xml.sax
+ org.xml.sax.helpers
+Export-Package
+ aQute.bnd.build {version=2.4.0}
+ aQute.bnd.header {version=1.3.0}
+ aQute.bnd.maven.support {version=2.0}
+ aQute.bnd.osgi {version=2.3.0}
+ aQute.bnd.osgi.resource {version=1.4.0}
+ aQute.bnd.service {version=4.1.0, imported-as=[4.1,5)}
+ aQute.bnd.service.action {version=2.0.0, imported-as=[2.0,2.1)}
+ aQute.bnd.service.classparser {version=1.0}
+ aQute.bnd.service.diff {version=1.0, imported-as=[1.0,2)}
+ aQute.bnd.service.extension {version=1.0}
+ aQute.bnd.service.progress {version=1.0.0, imported-as=[1.0,2)}
+ aQute.bnd.service.repository {version=1.2, imported-as=[1.2,2)}
+ aQute.bnd.service.resolve.hook {version=1.0}
+ aQute.bnd.service.url {version=1.2, imported-as=[1.2,2)}
+ aQute.bnd.util.dto {version=1.0}
+ aQute.bnd.version {version=1.1.0, imported-as=[1.1,2)}
+ aQute.service.reporter {version=1.0.1, imported-as=[1.0,2)}
+ org.osgi.resource {version=1.0, imported-as=[1.0,2)}
+ org.osgi.service.bindex {version=1.0}
+ org.osgi.service.repository {version=1.0}
+
Execute a Project action, or if no parms given, show information about the project
+ +[ -p, --project <string> ] - Identify another project
+
biz.aQute.bnd (master)$ bnd project
+Name biz.aQute.bnd
+Actions [build, test, run, clean, release, refresh, deploy]
+Directory /Ws/bnd/biz.aQute.bnd
+Depends on [aQute.libg, biz.aQute.bndlib, biz.aQute.junit, biz.aQute.launcher]
+Sub builders [biz.aQute.bnd]
+
Release this project
+ +[ -p, --project <string> ] - Path to project, default is current project
+[ -r, --repo <string> ] - Set the release repository
+[ -t, --test ] - Release with test build
+[ -w, --workspace ] - Release all bundles in in the workspace
+
Access to the repositories. Provides a number of sub commands to manipulate the repository (see repo help) that provide access to the installed repos for the current project.
+ +[ -c, --cache ] - Include the cache repository
+[ -f, --filerepo <string>* ] - Add a File Repository
+[ -m, --maven ] - Include the maven repository
+[ -p, --project <string> ] - Specify a project
+[ -r, --release <glob> ] - Override the name of the release repository
+ (-releaserepo)
+Available sub-commands:
+
+ copy -
+ diff - Diff jars (or show tree)
+ get - Get an artifact from a repository.
+ list - List all artifacts from the current repositories
+ with their versions
+ put - Put an artifact into the repository after it has
+ been verified.
+ refresh - Refresh refreshable repositories
+ repos - List the current repositories
+ versions - Displays a list of versions for a given bsn that
+ can be found in the current repositories.
+
copy [options] <source> <dest>
+
[ -d, –dry ] - Do not really copy but trace the steps
+ +Show the diff tree of a single repo or compare 2 +repos. A diff tree is a detailed tree of all +aspects of a bundle, including its packages, +types, methods, fields, and modifiers.
+ +diff [options] <newer repo> <[older repo]>
+
[ -a, --added ] - Just additions (no removes)
+[ -A, --all ] - Both add and removes
+[ -d, --diff ] - Formatted like diff
+[ -f, --full ] - Show full diff tree (also wen entries are equal)
+[ -j, --json ] - Serialize to JSON
+[ -r, --remove ] - Just removes (no additions)
+
Get an artifact from a repository.
+ +get [options] <bsn> <[range]>
+
[ -f, --from <instruction> ] -
+[ -l, --lowest ] -
+[ -o, --output <string> ] - Where to store the artifact
+
List all artifacts from the current repositories with their versions
+ +list [options]
+
[ -f, --from <instruction> ] - A glob expression on the source repo, default
+ is all repos
+[ -n, --noversions ] - Do not list the versions, just the bsns
+[ -q, --query <string> ] - Optional search term for the list of bsns (given
+ to the repo)
+
Put an artifact into the repository after it has been verified.
+ +put [options] <<jar>...>
+
[ -f, --force ] - Put in repository even if verification fails
+ (actually, no verification is done).
+
Refresh refreshable repositories
+ +refresh
+
List the current repositories
+ +repos
+
Displays a sorted set of versions for a given bsn that can be found in the current repositories.
+ +versions <bsn>
+
Run a project in the OSGi launcher. If not bndrun is specified, the current project is used for the run specification
+ +[ -p, --project <string> ] - Path to another project than the current
+ project. Only valid if no bndrun is specified
+[ -v, --verify ] - Verify all the dependencies before launching
+ (runpath, runbundles)
+
Run OSGi tests and create report
+ +runtests [options] …
+ +[ -d, --dir <string> ] - Path to work directory
+[ -e, --exclude <string;> ] - Exclude files by pattern
+[ -r, --reportdir <string> ] - Report directory
+[ -t, --tests <string;> ] - Test names to execute
+[ -T, --title <string> ] - Title in the report
+[ -v, --verbose ] - prints more processing information
+[ -w, --workspace <string> ] - Use the following workspace
+
bnd runtests --tests org.osgi.test.cases.tracker.junit.BundleTrackerTests:testSubclass,org.osgi.test.cases.tracker.junit.BundleTrackerTests:testModified org.osgi.test.cases.tracker.bnd
+
Print out the packages from spec jars and check in which ees they appear. Very specific. For example, schema ee.j2se-1.6.0 ee.j2se-1.5.0 ee.j2ee-1.4.0
+ +Is used to start package versioning and figure out the semantic history
+ +[ -o, –output
Helps finding information in a set of JARs by filtering on manifest data and printing out selected information.
+ +[ -h, --header <string>* ] - A manifest header to print or: path, name, size,
+ length, modified for information about the file,
+ wildcards are allowed to print multiple headers.
+[ -k, --key ] - Print the key before the value
+[ -n, --name ] - Print the file name before the value
+[ -p, --path ] - Print the file path before the value
+[ -w, --where <string> ] - A simple assertion on a manifest header (e.g.
+ Bundle-Version=1.0.1) or an OSGi filter that is
+ asserted on all manifest headers. Comparisons
+ are case insensitive. The key 'resources' holds
+ the pathnames of all resources and can also be
+ asserted to check for the presence of a header.
+
biz.aQute.bnd (master)$ bnd select -h name generated/*.jar
+biz.aQute.bnd.jar
+biz.aQute.bnd (master)$ bnd select -h size generated/*.jar
+2604654
+
Set bnd global variables. The key can be wildcarded.
+ +[ -b, --base64 ] - Show key in base64
+[ -c, --clear ] - Clear all the settings, including the public and
+ private key
+[ -g, --generate ] - Generate a new private/public key pair
+[ -l, --location <string> ] - Override the default "~/.bnd/settings.json"
+ location
+[ -m, --mac ] - Sign the strings on the commandline
+[ -p, --password <[c> ] - Password for local file
+[ -P, --publicKey ] - Show the public key
+[ -s, --secretKey ] - Show the private secret key
+
The shell
function in bnd is primarily intended to exercise macros. Although the macro
command made it possible to test a single macro, the awful interaction between the (ba)sh character interpretations for $ and quotes made this quite hard to use in practice. the shell therefore directly talks the macro language as you write it in a bnd.bnd file. Additionally, all bnd commands are also available.
$ bnd shell
+Base Project com.example.project
+> p
+com.example.project
+> now
+Fri Sep 28 11:29:02 CEST 2018
+>
+
When you start the shell bnd will try to find a project. If the -p
options is specified it will first look in that directory, otherwise it will look in the current working directory. If no project is found, it will try to find the workspace set by bnd. If no workspace can be found, bnd will use the bnd defaults as properties.
A project inherits all properties from the workspace. So when bnd has a project in scope then all macros and properties are available defined in the project’s bnd.bnd
file, the ./cnf/build.bnd
file, and any files in ./cnf/ext/*.bnd
. For example, javac.source
is a property set by the workspace:
> javac.source
+1.8
+>
+
This raises the question: What properties are there? The shell also supports bnd commands and there is a properties
command. However, there are a large number of properties so lets limit it to the properties that start with java:
> properties -k java*
+javac.compliance 1.8
+javac.source 1.8
+javac.target 1.8
+>
+
-p, --project path-to-project
+
Merge a binary jar with its sources. It is possible to specify source path
+ +[ -o, --output <string> ] - The output file
+
Execute a Project action, or if no parms given, show information about the project
+ +[ -p, --project <string> ] - Identify another project
+
Access the internal bnd database of keywords and options
+ +[ -w, --width <int> ] - The width of the printout
+
biz.aQute.bnd (master)$ bnd syntax Bundle-Version
+
+[Bundle-Version]
+ The Bundle-Version header specifies the version of this bundle.
+
+ Pattern : [0-9]{1,9}(\.[0-9]{1,9}(\.[0-9]{1,9}(\.[0-9A-Za-z_-]+)?)?)?
+ Example : Bundle-Version: 1.23.4.build200903221000
+
Test a project according to an OSGi test
+ +[ -c, --continuous ] - Set the -testcontinuous flag
+[ -f, --force ] - Launch the test even if this bundle does not
+ contain Test-Cases
+[ -p, --project <string> ] - Path to another project than the current project
+[ -t, --trace ] - Set the -runtrace flag
+[ -v, --verify ] - Verify all the dependencies before launching
+ (runpath, runbundles, testpath)
+
List files int a JAR file, equivalent jar command t[vf] (syntax supported)
+ +[ -f, --file <string> ] - Jar file (f option)
+[ -v, --verbose ] - Verbose (v option)
+
Verify jars
+ +Show version information about bnd
+ +[ -x, --xtra ] - Show licensing, copyright, sha, scm, etc
+
biz.aQute.bnd (master)$ bnd version -x
+Version 2.4.0.201407091507
+From Wed Jul 09 17:07:14 CEST 2014
+License Apache License, Version 2.0
+Copyright Copyright (c) aQute (2000, 2014). All Rights Reserved.
+Git-SHA c1ad07dfeb4704ce590bd93c1405d7bfe8bef131
+Git-Descriptor 2.4.0.M1-66-gc1ad07d-dirty
+Sources git://github.com/bndtools/bnd.git
+biz.aQute.bnd (master)$
+
View a resource from a JAR file. Manifest will be pretty printed and class files are shown disassembled.
+ +[ -c, --charset <string> ] - Character set to use for viewing
+
Wrap a jar into a bundle. This is a poor man’s facility to quickly turn a non-OSGi JAR into an OSGi bundle. It is usually better to write a bnd file and use the bnd
The wrap command takes an existing JAR file and guesses the manifest headers that will make this JAR useful for an OSGi Service Platform. If the output file is not overridden, the name of the input file is used with a .bar extension. The default bnd file for the header calculation is:
+ +Export-Package: *
+Import-Package: <packages inside the target jar>
+
If the target bundle has a manifest, the headers are merged with the properties.
+ +The defaults can be overridden with a specific properties file.
+ +[ -b, --bsn <string> ] - Set the bundle symbolic name to use
+[ -c, --classpath <string>* ] - A classpath specification
+[ -f, --force ] - Allow override of an existing file
+[ -i, -ignoremanifest ] - Do not include the manifest headers from the target bundle
+[ -o, --output <string> ] - Path to the output, default the name of the
+ input jar with the '.bar' extension. If this is
+ a directory, the output is place there.
+[ -p, --properties <string> ] - A file with properties in bnd format.
+[ -v, --version <version> ] - Set the version to use
+
` bnd wrap -classpath osgi.jar *.jar`
+ +Compares two XML resource repositories
+ +[ -e, --expandfilter ] - Expand 'filter' directives
+[ -i, --ignore <string> ] - Ignore elements from the comparison result (Format: type=name,..) e.g.
+ RESOURCE_ID=org.apache.felix.scr#com.company.runtime.*,
+ CAPABILITY=bnd.workspace.project#osgi.wiring.package:javax.xml.*,
+ ATTRIBUTE=bundle-symbolic-name:system.bundle,
+ REQUIREMENT=osgi.wiring.package:org.xml.*
+[ -s, --showall ] - Display all (changed and unchanged both)
+
MAJOR REPOSITORY <repository>
+ MAJOR RESOURCE_ID org.apache.felix.configadmin
+ MAJOR CAPABILITIES <capabilities>
+ MAJOR CAPABILITY bnd.maven:org.apache.felix:org.apache.felix.configadmin
+ REMOVED ATTRIBUTE maven-version:1.9.10
+ ADDED ATTRIBUTE maven-version:1.9.22
+ REMOVED CAPABILITY osgi.content:77B03B938E796C0512D9AD89ACF287CCE09C14A159CA05B6CB74DDA17E7AB3FA
+ REMOVED ATTRIBUTE mime:application/vnd.osgi.bundle
+ REMOVED ATTRIBUTE size:155630
+ REMOVED ATTRIBUTE url:file:/Users/amit/.m2/repository/org/apache/felix/org.apache.felix.configadmin/1.9.10/org.apache.felix.configadmin-1.9.10.jar
+ ADDED CAPABILITY osgi.content:B349E16D60DA66B6DA70AB3E056677A9D6A0B8953DF84ECD63B10AA5EF3C5865
+ ADDED ATTRIBUTE mime:application/vnd.osgi.bundle
+ ADDED ATTRIBUTE size:168301
+ ADDED ATTRIBUTE url:file:/Users/amit/.m2/repository/org/apache/felix/org.apache.felix.configadmin/1.9.22/org.apache.felix.configadmin-1.9.22.jar
+ MAJOR CAPABILITY osgi.identity:org.apache.felix.configadmin
+ REMOVED ATTRIBUTE version:1.9.10
+ ADDED ATTRIBUTE version:1.9.22
+ MAJOR CAPABILITY osgi.wiring.bundle:org.apache.felix.configadmin
+ REMOVED ATTRIBUTE bundle-version:1.9.10
+ ADDED ATTRIBUTE bundle-version:1.9.22
+ MAJOR CAPABILITY osgi.wiring.host:org.apache.felix.configadmin
+ REMOVED ATTRIBUTE bundle-version:1.9.10
+ ADDED ATTRIBUTE bundle-version:1.9.22
+ MAJOR CAPABILITY osgi.wiring.package:org.apache.felix.cm
+ REMOVED ATTRIBUTE bundle-version:1.9.10
+ ADDED ATTRIBUTE bundle-version:1.9.22
+ MAJOR CAPABILITY osgi.wiring.package:org.apache.felix.cm.file
+ REMOVED ATTRIBUTE bundle-version:1.9.10
+ ADDED ATTRIBUTE bundle-version:1.9.22
+ MAJOR CAPABILITY osgi.wiring.package:org.osgi.service.cm
+ REMOVED ATTRIBUTE bundle-version:1.9.10
+ ADDED ATTRIBUTE bundle-version:1.9.22
+ REMOVED VERSION 1.9.10
+ ADDED VERSION 1.9.22
+ MAJOR RESOURCE_ID org.apache.felix.eventadmin
+ MAJOR REQUIREMENTS <requirements>
+ MAJOR REQUIREMENT osgi.ee:JavaSE
+ REMOVED DIRECTIVE filter:(&(osgi.ee=JavaSE)(version=1.7))
+ ADDED DIRECTIVE filter:(&(osgi.ee=JavaSE)(version=1.8))
+ MAJOR CAPABILITIES <capabilities>
+ MAJOR CAPABILITY bnd.maven:org.apache.felix:org.apache.felix.eventadmin
+ REMOVED ATTRIBUTE maven-version:1.5.0
+ ADDED ATTRIBUTE maven-version:1.6.2
+ ADDED CAPABILITY osgi.content:445A90F6E31CDE9635C474CEA286273481D2E6EE293B52D8FC42ED8E927B5604
+ ADDED ATTRIBUTE mime:application/vnd.osgi.bundle
+ ADDED ATTRIBUTE size:83611
+ ADDED ATTRIBUTE url:file:/Users/amit/.m2/repository/org/apache/felix/org.apache.felix.eventadmin/1.6.2/org.apache.felix.eventadmin-1.6.2.jar
+ REMOVED CAPABILITY osgi.content:A433A9020E1EAD82494AA6611E8A644F88733BD0278F349D6BEA3B2E448DDD71
+ REMOVED ATTRIBUTE mime:application/vnd.osgi.bundle
+ REMOVED ATTRIBUTE size:81529
+ REMOVED ATTRIBUTE url:file:/Users/amit/.m2/repository/org/apache/felix/org.apache.felix.eventadmin/1.5.0/org.apache.felix.eventadmin-1.5.0.jar
+ MAJOR CAPABILITY osgi.identity:org.apache.felix.eventadmin
+ REMOVED ATTRIBUTE version:1.5.0
+ ADDED ATTRIBUTE version:1.6.2
+ MAJOR CAPABILITY osgi.wiring.bundle:org.apache.felix.eventadmin
+ REMOVED ATTRIBUTE bundle-version:1.5.0
+ ADDED ATTRIBUTE bundle-version:1.6.2
+ MAJOR CAPABILITY osgi.wiring.host:org.apache.felix.eventadmin
+ REMOVED ATTRIBUTE bundle-version:1.5.0
+ ADDED ATTRIBUTE bundle-version:1.6.2
+ MAJOR CAPABILITY osgi.wiring.package:org.osgi.service.event
+ REMOVED ATTRIBUTE bundle-version:1.5.0
+ ADDED ATTRIBUTE bundle-version:1.6.2
+ REMOVED VERSION 1.5.0
+ ADDED VERSION 1.6.2
+ REMOVED RESOURCE_ID org.osgi.util.promise
+ REMOVED REQUIREMENTS <requirements>
+ REMOVED REQUIREMENT osgi.ee:JavaSE
+ REMOVED DIRECTIVE filter:(&(osgi.ee=JavaSE)(version=1.7))
+ REMOVED REQUIREMENT osgi.wiring.package:org.osgi.util.function
+ REMOVED DIRECTIVE filter:(&(osgi.wiring.package=org.osgi.util.function)(version>=1.1.0)(!(version>=2.0.0)))
+ REMOVED CAPABILITIES <capabilities>
+ REMOVED CAPABILITY bnd.maven:org.osgi:org.osgi.util.promise
+ REMOVED ATTRIBUTE maven-classifier:
+ REMOVED ATTRIBUTE maven-extension:jar
+ REMOVED ATTRIBUTE maven-repository:Runtime
+ REMOVED ATTRIBUTE maven-version:1.1.1
+ REMOVED CAPABILITY osgi.content:4F85BECCD281CC1A4E735BD266A0DD3DB11651D3D0DDE001E6BFA55DBDFDEE83
+ REMOVED ATTRIBUTE mime:application/vnd.osgi.bundle
+ REMOVED ATTRIBUTE size:75587
+ REMOVED ATTRIBUTE url:file:/Users/amit/.m2/repository/org/osgi/org.osgi.util.promise/1.1.1/org.osgi.util.promise-1.1.1.jar
+ REMOVED CAPABILITY osgi.identity:org.osgi.util.promise
+ REMOVED ATTRIBUTE copyright:Copyright (c) OSGi Alliance (2000, 2018). All Rights Reserved.
+ REMOVED ATTRIBUTE description:OSGi Companion Code for org.osgi.util.promise Version 1.1.1
+ REMOVED ATTRIBUTE documentation:https://www.osgi.org/
+ REMOVED ATTRIBUTE license:Apache-2.0; link="http://www.apache.org/licenses/LICENSE-2.0"; description="Apache License, Version 2.0"
+ REMOVED ATTRIBUTE type:osgi.bundle
+ REMOVED ATTRIBUTE version:1.1.1.201810101357
+ REMOVED CAPABILITY osgi.wiring.bundle:org.osgi.util.promise
+ REMOVED ATTRIBUTE bundle-version:1.1.1.201810101357
+ REMOVED CAPABILITY osgi.wiring.host:org.osgi.util.promise
+ REMOVED ATTRIBUTE bundle-version:1.1.1.201810101357
+ REMOVED CAPABILITY osgi.wiring.package:org.osgi.util.promise
+ REMOVED ATTRIBUTE bnd.hashes:-1923478059
+ REMOVED ATTRIBUTE bundle-symbolic-name:org.osgi.util.promise
+ REMOVED ATTRIBUTE bundle-version:1.1.1.201810101357
+ REMOVED ATTRIBUTE version:1.1.1
+ REMOVED DIRECTIVE uses:org.osgi.util.function
+ REMOVED VERSION 1.1.1.201810101357
+ REMOVED RESOURCE_ID org.osgi.util.pushstream
+ REMOVED REQUIREMENTS <requirements>
+ REMOVED REQUIREMENT osgi.ee:JavaSE/compact1
+ REMOVED DIRECTIVE filter:(&(osgi.ee=JavaSE/compact1)(version=1.8))
+ REMOVED REQUIREMENT osgi.wiring.package:org.osgi.util.function
+ REMOVED DIRECTIVE filter:(&(osgi.wiring.package=org.osgi.util.function)(version>=1.1.0)(!(version>=2.0.0)))
+ REMOVED REQUIREMENT osgi.wiring.package:org.osgi.util.promise
+ REMOVED DIRECTIVE filter:(&(osgi.wiring.package=org.osgi.util.promise)(version>=1.1.0)(!(version>=2.0.0)))
+ REMOVED CAPABILITIES <capabilities>
+ REMOVED CAPABILITY bnd.maven:org.osgi:org.osgi.util.pushstream
+ REMOVED ATTRIBUTE maven-classifier:
+ REMOVED ATTRIBUTE maven-extension:jar
+ REMOVED ATTRIBUTE maven-repository:Runtime
+ REMOVED ATTRIBUTE maven-version:1.0.1
+ REMOVED CAPABILITY osgi.content:1E0C9D435A107444A4461788E62BDDC94715E444AFDBC54417593ECA4BB50CE2
+ REMOVED ATTRIBUTE mime:application/vnd.osgi.bundle
+ REMOVED ATTRIBUTE size:132226
+ REMOVED ATTRIBUTE url:file:/Users/amit/.m2/repository/org/osgi/org.osgi.util.pushstream/1.0.1/org.osgi.util.pushstream-1.0.1.jar
+ REMOVED CAPABILITY osgi.identity:org.osgi.util.pushstream
+ REMOVED ATTRIBUTE copyright:Copyright (c) OSGi Alliance (2000, 2018). All Rights Reserved.
+ REMOVED ATTRIBUTE description:OSGi Companion Code for org.osgi.util.pushstream Version 1.0.1
+ REMOVED ATTRIBUTE documentation:https://www.osgi.org/
+ REMOVED ATTRIBUTE license:Apache-2.0; link="http://www.apache.org/licenses/LICENSE-2.0"; description="Apache License, Version 2.0"
+ REMOVED ATTRIBUTE type:osgi.bundle
+ REMOVED ATTRIBUTE version:1.0.1.201810101357
+ REMOVED CAPABILITY osgi.wiring.bundle:org.osgi.util.pushstream
+ REMOVED ATTRIBUTE bundle-version:1.0.1.201810101357
+ REMOVED CAPABILITY osgi.wiring.host:org.osgi.util.pushstream
+ REMOVED ATTRIBUTE bundle-version:1.0.1.201810101357
+ REMOVED CAPABILITY osgi.wiring.package:org.osgi.util.pushstream
+ REMOVED ATTRIBUTE bnd.hashes:-1923478059
+ REMOVED ATTRIBUTE bundle-symbolic-name:org.osgi.util.pushstream
+ REMOVED ATTRIBUTE bundle-version:1.0.1.201810101357
+ REMOVED ATTRIBUTE version:1.0.1
+ REMOVED DIRECTIVE uses:org.osgi.util.function,org.osgi.util.promise
+ REMOVED VERSION 1.0.1.201810101357
+
Show a cross references for all classes in a set of jars.
+ +[ -c, --classes ] - Show classes instead of packages
+[ -f, --from ] - Show references from other classes/packages (<)
+[ -m, --match <string>* ] - Filter for class names, a globbing expression
+[ -t, --to ] - Show references to other classes/packages (>)
+
biz.aQute.bnd (master)$ bnd xref generated/*.jar
+ aQute.bnd.annotation >
+ aQute.bnd.annotation.component >
+ aQute.bnd.annotation.headers >
+ aQute.bnd.annotation.licenses >
+ aQute.bnd.annotation.metatype >
+ aQute.bnd.ant > aQute.service.reporter
+ org.apache.tools.ant
+ aQute.libg.reporter
+ org.apache.tools.ant.taskdefs
+ aQute.bnd.osgi
+ aQute.bnd.build
+ aQute.libg.qtokens
+ org.apache.tools.ant.types
+ aQute.bnd.osgi.eclipse
+ aQute.bnd.service.progress
+ aQute.bnd.service
+ aQute.bnd.version
+ aQute.bnd.build.model
+ aQute.bnd.build.model.clauses
+ aQute.bnd.build > aQute.service.reporter
+ aQute.bnd.osgi
+ aQute.bnd.service
+ aQute.libg.command
+ aQute.libg.sed
+ aQute.bnd.version
+ aQute.bnd.service.action
+ aQute.bnd.header
+ aQute.lib.io
+ aQute.libg.reporter
+ aQute.bnd.osgi.eclipse
+ aQute.bnd.help
+ aQute.lib.strings
+ aQute.libg.generics
+ aQute.bnd.maven.support
+ aQute.libg.glob
+ aQute.lib.converter
+ aQute.lib.collections
+ aQute.bnd.differ
+ aQute.bnd.service.diff
+ aQute.bnd.service.repository
+ aQute.lib.deployer
+ javax.naming
+ aQute.lib.hex
+ aQute.bnd.resource.repository
+ aQute.bnd.url
+ aQute.lib.settings
+ aQute.bnd.service.url
+ aQute.bnd.service.extension
+ aQute.bnd.build.model > aQute.bnd.build.model.conversions
+ aQute.libg.tuple
+ aQute.bnd.build.model.clauses
+ aQute.bnd.header
+ aQute.bnd.properties
+ aQute.bnd.build
+ aQute.bnd.version
+ aQute.lib.io
+ org.osgi.resource
+ aQute.bnd.osgi
+ aQute.bnd.build.model.clauses > aQute.bnd.header
+ aQute.bnd.build.model.conversions > aQute.bnd.header
+ aQute.libg.tuple
+ aQute.bnd.osgi
+ aQute.bnd.build.model
+ aQute.bnd.build.model.clauses
+ org.osgi.resource
+ aQute.bnd.osgi.resource
+ aQute.libg.qtokens
+ aQute.bnd.compatibility > aQute.bnd.osgi
+ aQute.bnd.component > aQute.bnd.osgi
+ aQute.service.reporter
+ aQute.lib.collections
+ org.osgi.service.component.annotations
+ aQute.bnd.version
+ aQute.bnd.component.error
+ aQute.lib.tag
+ aQute.bnd.header
+ aQute.bnd.service
+ aQute.bnd.component.error >
+ aQute.bnd.differ > aQute.bnd.header
+ aQute.bnd.service.diff
+ aQute.bnd.osgi
+ aQute.bnd.version
+ aQute.service.reporter
+ aQute.libg.generics
+ aQute.libg.cryptography
+ aQute.lib.hex
+ aQute.lib.io
+ aQute.lib.collections
+ aQute.bnd.annotation
+ aQute.bnd.service
+ aQute.bnd.enroute.commands > aQute.lib.getopt
+ aQute.bnd.osgi
+ aQute.service.reporter
+ aQute.bnd.main
+ aQute.bnd.build
+ aQute.lib.io
+ aQute.bnd.filerepo > aQute.bnd.version
+ aQute.bnd.header > aQute.bnd.version
+ aQute.bnd.osgi
+ aQute.service.reporter
+ aQute.libg.generics
+ aQute.libg.qtokens
+ aQute.lib.collections
+ aQute.bnd.help > aQute.bnd.osgi
+
public void setup(Bundle fw, Bundle targetBundle) {
+ startTime = System.currentTimeMillis();
+
+ testsuite.addAttribute("hostname", hostname);
+ if (targetBundle != null) {
+ testsuite.addAttribute("name", "test." + targetBundle.getSymbolicName());
+ testsuite.addAttribute("target", targetBundle.getLocation());
+ } else {
+ testsuite.addAttribute("name", "test.run");
+
+ }
+ testsuite.addAttribute("timestamp", df.format(new Date()));
+ testsuite.addAttribute("framework", fw);
+ testsuite.addAttribute("framework-version", fw.getVersion());
+
+ Tag properties = new Tag("properties");
+ testsuite.addContent(properties);
+
+ for (Map.Entry<Object,Object> entry : System.getProperties().entrySet()) {
+ Tag property = new Tag(properties, "property");
+ property.addAttribute("name", entry.getKey());
+ property.addAttribute("value", entry.getValue());
+ }
+
+ Tag bundles = new Tag(testsuite, "bundles");
+ Bundle bs[] = fw.getBundleContext().getBundles();
+
+ for (int i = 0; i < bs.length; i++) {
+ Tag bundle = new Tag("bundle");
+ bundle.addAttribute("location", bs[i].getLocation());
+ bundle.addAttribute("modified", df.format(new Date(bs[i].getLastModified())));
+ bundle.addAttribute("state", bs[i].getState());
+ bundle.addAttribute("id", bs[i].getBundleId() + "");
+ if ( bs[i].getSymbolicName() != null)
+ bundle.addAttribute("bsn", bs[i].getSymbolicName());
+ if ( bs[i].getVersion() != null)
+ bundle.addAttribute("version", bs[i].getVersion());
+
+ if (bs[i].equals(targetBundle))
+ bundle.addAttribute("target", "true");
+
+ bundles.addContent(bundle);
+ }
+ if (targetBundle != null) {
+ String header = (String) targetBundle.getHeaders().get(aQute.bnd.osgi.Constants.BND_ADDXMLTOTEST);
+ if (header != null) {
+ StringTokenizer st = new StringTokenizer(header, " ,");
+
+ while (st.hasMoreTokens()) {
+ String resource = st.nextToken();
+ URL url = targetBundle.getEntry(resource);
+
+ if (url != null) {
+ String name = url.getFile();
+ int n = name.lastIndexOf('/');
+ if (n < 0)
+ n = 0;
+ else
+ n = n + 1;
+
+ if (name.endsWith(".xml"))
+ name = name.substring(n, name.length() - 4);
+ else
+ name = name.substring(n, name.length()).replace('.', '_');
+
+ testsuite.addContent(url);
+
+ } else {
+ Tag addxml = new Tag(testsuite, "error");
+ addxml.addAttribute("reason", "no such resource: " + resource);
+ }
+ }
+ }
+ }
+}
+
if (!noExtraHeaders) {
+ main.putValue(CREATED_BY, System.getProperty("java.version") + " (" + System.getProperty("java.vendor")
+ + ")");
+ main.putValue(TOOL, "Bnd-" + getBndVersion());
+ main.putValue(BND_LASTMODIFIED, "" + System.currentTimeMillis());
+ }
+
Activation Policies +The activation of a bundle can also be deferred to a later time from its start using an activation policy. This policy is specified in the Bundle-ActivationPolicy header with the following syntax: +Bundle-ActivationPolicy ::= policy ( ‘;’ directive )* +policy ::= ‘lazy’ +The only policy defined is the lazy activation policy. If no Bundle-ActivationPolicy header is speci- fied, the bundle will use eager activation. +Lazy Activation Policy +A lazy activation policy indicates that the bundle, once started, must not be activated until it re- ceives the first request to load a class. This request can originate either during normal class load- ing or via the Bundle loadClass method. Resource loading and a request for a class that is re-direct- ed to another bundle must not trigger the activation. The first request is relative to the bundle class loader, a bundle will not be lazily started if it is stopped and then started again without being re- freshed in the mean time. +This change from the default eager activation policy is reflected in the state of the bundle and its events. When a bundle is started using a lazy activation policy, the following steps must be taken: +• A Bundle Context is created for the bundle. +• The bundle state is moved to the STARTING state. +• The LAZY_ACTIVATION event is fired. +• The system waits for a class load from the bundle to occur. +• The normal STARTING event is fired. +• The bundle is activated. +• The bundle state is moved to ACTIVE. +• The STARTED event is fired. +If the activation fails because the Bundle Activator start method has thrown an exception, the bun- dle must be stopped without calling the Bundle Activator stop method. These steps are pictured in a flow chart in Figure 4.5. This flow chart also shows the difference in activation policy of the normal eager activation and the lazy activation. +• +Page 110 +OSGi Core Release 6 +Life Cycle Layer Version 1.8 +The Bundle Object +Figure 4.5 +Starting with eager activation versus lazy activation +started? no +yes +state=STARTING +lazyactivation? yes no +exception? +Waitforclass load trigger +event LAZY_ACTIVATION +event STARTING +activate the bundle +yes +no +state=STOPPING event STOPPING state=RESOLVED event STOPPED +state=ACTIVE event STARTED +The lazy activation policy allows a Framework implementation to defer the creation of the bundle class loader and activation of the bundle until the bundle is first used; potentially saving resources and initialization time during startup. +By default, any class loaded from the bundle can trigger the lazy activation, however, resource loads must not trigger the activation. The lazy activation policy can define which classes cause the activa- tion with the following directives: +• include - A list of package names that must trigger the activation when a class is loaded from any of these packages. The default is all package names present in the bundle. +• exclude - A list of package names that must not trigger the activation of the bundle when a class is loaded from any of these packages. The default is no package names. +For example: +Bundle-ActivationPolicy: lazy; « + include:=”com.acme.service.base,com.acme.service.help” +When a class load triggers the lazy activation, the Framework must first define the triggering class. This definition can trigger additional lazy activations. These activations must be deferred until all transitive class loads and defines have finished. Thereafter, the activations must be executed in the reverse order of detection. That is, the last detected activation must be executed first. Only after +OSGi Core Release 6 +Page 111 +The Bundle Object Life Cycle Layer Version 1.8 +4.4.6.3 +all deferred activations are finished must the class load that triggered the activation return with the loaded class. If an error occurs during this process, it should be reported as a Framework ERROR event. However, the class load must succeed normally. A bundle that fails its lazy activation should not be activated again until the framework is restarted or the bundle is explicitly started by calling the Bundle start method.
+ +public boolean verifyActivationPolicy(String policy) {
+ Parameters map = parseHeader(policy);
+ if (map.size() == 0)
+ warning(Constants.BUNDLE_ACTIVATIONPOLICY + " is set but has no argument %s", policy);
+ else if (map.size() > 1)
+ warning(Constants.BUNDLE_ACTIVATIONPOLICY + " has too many arguments %s", policy);
+ else {
+ Map<String,String> s = map.get("lazy");
+ if (s == null)
+ warning(Constants.BUNDLE_ACTIVATIONPOLICY + " set but is not set to lazy: %s", policy);
+ else
+ return true;
+ }
+
+ return false;
+}
+
private void verifyActivator() throws Exception {
+ String bactivator = main.get(Constants.BUNDLE_ACTIVATOR);
+ if (bactivator != null) {
+ TypeRef ref = analyzer.getTypeRefFromFQN(bactivator);
+ if (analyzer.getClassspace().containsKey(ref)) {
+ Clazz activatorClazz = analyzer.getClassspace().get(ref);
+
+ if (activatorClazz.isInterface()) {
+ registerActivatorErrorLocation(error("The Bundle Activator " + bactivator +
+ " is an interface and therefore cannot be instantiated."),
+ bactivator, ActivatorErrorType.IS_INTERFACE);
+ } else {
+ if(activatorClazz.isAbstract()) {
+ registerActivatorErrorLocation(error("The Bundle Activator " + bactivator +
+ " is abstract and therefore cannot be instantiated."),
+ bactivator, ActivatorErrorType.IS_ABSTRACT);
+ }
+ if(!activatorClazz.isPublic()) {
+ registerActivatorErrorLocation(error("Bundle Activator classes must be public, and " +
+ bactivator + " is not."), bactivator, ActivatorErrorType.NOT_PUBLIC);
+ }
+ if(!activatorClazz.hasPublicNoArgsConstructor()) {
+ registerActivatorErrorLocation(error("Bundle Activator classes must have a public zero-argument constructor and " +
+ bactivator + " does not."), bactivator, ActivatorErrorType.NO_SUITABLE_CONSTRUCTOR);
+ }
+
+ if (!activatorClazz.is(QUERY.IMPLEMENTS,
+ new Instruction("org.osgi.framework.BundleActivator"), analyzer)) {
+ registerActivatorErrorLocation(error("The Bundle Activator " + bactivator +
+ " does not implement BundleActivator."), bactivator, ActivatorErrorType.NOT_AN_ACTIVATOR);
+ }
+ }
+ return;
+ }
+
+ PackageRef packageRef = ref.getPackageRef();
+ if (packageRef.isDefaultPackage())
+ registerActivatorErrorLocation(error("The Bundle Activator is not in the bundle and it is in the default package "),
+ bactivator, ActivatorErrorType.DEFAULT_PACKAGE);
+ else if (!analyzer.isImported(packageRef)) {
+ registerActivatorErrorLocation(error(Constants.BUNDLE_ACTIVATOR +
+ " not found on the bundle class path nor in imports: " + bactivator),
+ bactivator, ActivatorErrorType.NOT_ACCESSIBLE);
+ } else {
+ registerActivatorErrorLocation(warning(Constants.BUNDLE_ACTIVATOR + " " + bactivator +
+ " is being imported into the bundle rather than being contained inside it. This is usually a bundle packaging error"),
+ bactivator, ActivatorErrorType.IS_IMPORTED);
+ }
+ }
+}
+
+
+
+
+ String s = getProperty(BUNDLE_ACTIVATOR);
+ if (s != null) {
+ activator = getTypeRefFromFQN(s);
+ referTo(activator);
+ trace("activator %s %s", s, activator);
+ }
+
public class SpringXMLType extends XMLTypeProcessor {
+
+@Override
+protected List<XMLType> getTypes(Analyzer analyzer) throws Exception {
+ List<XMLType> types = new ArrayList<XMLType>();
+
+ String header = analyzer.getProperty(Constants.BUNDLE_BLUEPRINT, "OSGI-INF/blueprint");
+ process(types, "extract.xsl", header, ".*\\.xml");
+ header = analyzer.getProperty("Spring-Context", "META-INF/spring");
+ process(types, "extract.xsl", header, ".*\\.xml");
+
+ return types;
+}
+
+}
+
/*
+ * Bundle-Category header
+ */
+private void doBundleCategory(BundleCategory annotation) {
+ if (annotation.custom() != null)
+ for (String s : annotation.custom()) {
+ add(Constants.BUNDLE_CATEGORY, s);
+ }
+
+ if (annotation.value() != null)
+ for (Category s : annotation.value()) {
+ add(Constants.BUNDLE_CATEGORY, s.toString());
+ }
+}
+
Defines the internal bundle class path, is taken into accont by bnd. That is, classes will be analyzed according to this path. The files/directories on the Bundle-ClassPath must be present in the bundle. Use Include-Resource to include these jars/directories in your bundle. In general you should not use Bundle-ClassPath since it makes things more complicated than necessary. Use the @ option in the Include-Resource to unroll the jars into the JAR.
+ +public void verifyBundleClasspath() {
+ Parameters bcp = main.getBundleClassPath();
+ if (bcp.isEmpty() || bcp.containsKey("."))
+ return;
+
+ for (String path : bcp.keySet()) {
+ if (path.endsWith("/"))
+ error("A " + Constants.BUNDLE_CLASSPATH + " entry must not end with '/': %s", path);
+
+ if (dot.getDirectories().containsKey(path))
+ // We assume that any classes are in a directory
+ // and therefore do not care when the bundle is included
+ return;
+ }
+
+ for (String path : dot.getResources().keySet()) {
+ if (path.endsWith(".class")) {
+ warning("The " + Constants.BUNDLE_CLASSPATH + " does not contain the actual bundle JAR (as specified with '.' in the " + Constants.BUNDLE_CLASSPATH + ") but the JAR does contain classes. Is this intentional?");
+ return;
+ }
+ }
+}
+
+
+
+
+ /**
+ * Check for unresolved imports. These are referrals that are not imported
+ * by the manifest and that are not part of our bundle class path. The are
+ * calculated by removing all the imported packages and contained from the
+ * referred packages.
+ * @throws Exception
+ */
+private void verifyUnresolvedReferences() throws Exception {
+
+ //
+ // If we're being called from the builder then this should
+ // already have been done
+ //
+
+ if (isFrombuilder())
+ return;
+
+ Manifest m = analyzer.getJar().getManifest();
+ if (m == null) {
+ error("No manifest");
+ }
+
+ Domain domain = Domain.domain(m);
+
+ Set<PackageRef> unresolvedReferences = new TreeSet<PackageRef>(analyzer.getReferred().keySet());
+ unresolvedReferences.removeAll(analyzer.getContained().keySet());
+ for ( String pname : domain.getImportPackage().keySet()) {
+ PackageRef pref = analyzer.getPackageRef(pname);
+ unresolvedReferences.remove(pref);
+ }
+
+ // Remove any java.** packages.
+ for (Iterator<PackageRef> p = unresolvedReferences.iterator(); p.hasNext();) {
+ PackageRef pack = p.next();
+ if (pack.isJava())
+ p.remove();
+ else {
+ // Remove any dynamic imports
+ if (isDynamicImport(pack))
+ p.remove();
+ }
+ }
+
+ //
+ // If there is a Require bundle, all bets are off and
+ // we cannot verify anything
+ //
+
+ if (domain.getRequireBundle().isEmpty() && domain.get("ExtensionBundle-Activator") == null
+ && (domain.getFragmentHost()== null || domain.getFragmentHost().getKey().equals("system.bundle"))) {
+
+ if (!unresolvedReferences.isEmpty()) {
+ // Now we want to know the
+ // classes that are the culprits
+ Set<String> culprits = new HashSet<String>();
+ for (Clazz clazz : analyzer.getClassspace().values()) {
+ if (hasOverlap(unresolvedReferences, clazz.getReferred()))
+ culprits.add(clazz.getAbsolutePath());
+ }
+
+ if (analyzer instanceof Builder)
+ warning("Unresolved references to %s by class(es) %s on the " + Constants.BUNDLE_CLASSPATH + ": %s",
+ unresolvedReferences, culprits, analyzer.getBundleClasspath().keySet());
+ else
+ error("Unresolved references to %s by class(es) %s on the " + Constants.BUNDLE_CLASSPATH + ": %s",
+ unresolvedReferences, culprits, analyzer.getBundleClasspath().keySet());
+ return;
+ }
+ } else if (isPedantic())
+ warning("Use of " + Constants.REQUIRE_BUNDLE + ", ExtensionBundle-Activator, or a system bundle fragment makes it impossible to verify unresolved references");
+}
+
+
+
+
+ if (dot.getDirectories().containsKey(path)) {
+ // if directories are used, we should not have dot as we
+ // would have the classes in these directories on the
+ // class path twice.
+ if (bcp.containsKey("."))
+ warning(Constants.BUNDLE_CLASSPATH
+ + " uses a directory '%s' as well as '.'. This means bnd does not know if a directory is a package.",
+ path, path);
+ analyzeJar(dot, Processor.appendPath(path) + "/", true);
+ } else {
+ if (!"optional".equals(info.get(RESOLUTION_DIRECTIVE)))
+ warning("No sub JAR or directory " + path);
+ }
+
+
+ Parameters bcp = getBundleClasspath();
+ if (bcp.isEmpty() || (bcp.containsKey(".") && bcp.size() == 1))
+ main.remove(BUNDLE_CLASSPATH);
+ else
+ main.putValue(BUNDLE_CLASSPATH, printClauses(bcp));
+
+ // ----- Require/Capabilities section
+
/*
+ * Bundle-Contributors header
+ */
+
+private void doBundleContributors(BundleContributors annotation) throws IOException {
+ StringBuilder sb = new StringBuilder(annotation.value());
+ if (annotation.name() != null) {
+ sb.append(";name='");
+ escape(sb, annotation.name());
+ sb.append("'");
+ }
+ if (annotation.roles() != null) {
+ sb.append(";roles='");
+ escape(sb,annotation.roles());
+ sb.append("'");
+ }
+ if (annotation.organizationUrl() != null) {
+ sb.append(";organizationUrl='");
+ escape(sb,annotation.organizationUrl());
+ sb.append("'");
+ }
+ if (annotation.organization() != null) {
+ sb.append(";organization='");
+ escape(sb,annotation.organization());
+ sb.append("'");
+ }
+ if (annotation.timezone() != 0)
+ sb.append(";timezone=").append(annotation.timezone());
+ add(Constants.BUNDLE_CONTRIBUTORS, sb.toString());
+}
+
+
+ /**
+ * Maven defines contributors and developers in the POM. This annotation will
+ * generate a (not standardized by OSGi) Bundle-Contributors header.
+ * <p>
+ * This annotation can be used directly on a type or it can 'color' an
+ * annotation. This coloring allows custom annotations that define a specific
+ * contributor. For example:
+ *
+ * <pre>
+ * {@code @}BundleContributor("Peter.Kriens@aQute.biz")
+ * {@code @}interface pkriens {}
+ *
+ * {@code @}pkriens
+ * public class MyFoo {
+ * ...
+ * }
+ * </pre>
+ *
+ * Duplicates are removed before the header is generated and the coloring does
+ * not create an entry in the header, only an annotation on an actual type is
+ * counted. This makes it possible to make a library of contributors without
+ * then adding them all to the header.
+ * <p>
+ * See <a href=https://maven.apache.org/pom.html#Developers>Maven POM reference</a>
+ */
+ @Retention(RetentionPolicy.CLASS)
+ @Target({
+ ElementType.ANNOTATION_TYPE, ElementType.TYPE
+ })
+ public @interface BundleContributors {
+
+ /**
+ * The email address of the developer.
+ */
+ String value();
+
+ /**
+ * The display name of the developer. If not specified, the {@link #value()}
+ * is used.
+ */
+ String name() default "";
+
+ /**
+ * The roles this contributor plays in the development.
+ */
+ String[] roles() default {};
+
+ /**
+ * The name of the organization where the contributor works for.
+ */
+ String organization() default "";
+
+ /**
+ * The url of the organization where the contributor works for.
+ */
+ String organizationUrl() default "";
+
+ /**
+ * Time offset in hours from UTC without Daylight savings
+ */
+ int timezone() default 0;
+ }
+
/*
+ * Bundle-Copyright header
+ */
+private void doBundeCopyright(BundleCopyright annotation) {
+ add(Constants.BUNDLE_COPYRIGHT, annotation.value());
+}
+
/*
+ * Bundle-Developers header
+ */
+private void doBundleDevelopers(BundleDevelopers annotation) throws IOException {
+ StringBuilder sb = new StringBuilder(annotation.value());
+ if (annotation.name() != null) {
+ sb.append(";name='");
+ escape(sb, annotation.name());
+ sb.append("'");
+ }
+ if (annotation.roles() != null) {
+ sb.append(";roles='");
+ escape(sb,annotation.roles());
+ sb.append("'");
+ }
+ if (annotation.organizationUrl() != null) {
+ sb.append(";organizationUrl='");
+ escape(sb,annotation.organizationUrl());
+ sb.append("'");
+ }
+ if (annotation.organization() != null) {
+ sb.append(";organization='");
+ escape(sb,annotation.organization());
+ sb.append("'");
+ }
+ if (annotation.timezone() != 0)
+ sb.append(";timezone=").append(annotation.timezone());
+
+ add(Constants.BUNDLE_DEVELOPERS, sb.toString());
+}
+
+
+ /**
+ * Maven defines developers and developers in the POM. This annotation will
+ * generate a (not standardized by OSGi) Bundle-Developers header.
+ * <p>
+ * A deve
+ * <p>
+ * This annotation can be used directly on a type or it can 'color' an
+ * annotation. This coloring allows custom annotations that define a specific
+ * developer. For example:
+ *
+ * <pre>
+ * @BundleContributor("Peter.Kriens@aQute.biz")
+ * @interface pkriens {}
+ *
+ * @pkriens
+ * public class MyFoo {
+ * ...
+ * }
+ * </pre>
+ *
+ * Duplicates are removed before the header is generated and the coloring does
+ * not create an entry in the header, only an annotation on an actual type is
+ * counted. This makes it possible to make a library of developers without
+ * then adding them all to the header.
+ * <p>
+ * {@see https://maven.apache.org/pom.html#Developers}
+ */
+ @Retention(RetentionPolicy.CLASS)
+ @Target({
+ ElementType.ANNOTATION_TYPE, ElementType.TYPE
+ })
+ public @interface BundleDevelopers {
+
+ /**
+ * The email address of the developer.
+ */
+ String value();
+
+ /**
+ * The display name of the developer. If not specified, the {@link #value()}
+ * is used.
+ */
+ String name() default "";
+
+ /**
+ * The roles this developer plays in the development.
+ */
+ String[] roles() default {};
+
+ /**
+ * The name of the organization where the developer works for.
+ */
+ String organization() default "";
+
+ /**
+ * The url of the organization where the developer works for.
+ */
+ String organizationUrl() default "";
+
+ /**
+ * Time offset in hours from UTC without Daylight savings
+ */
+ int timezone() default 0;
+ }
+
/*
+ * Bundle-DocURL header
+ */
+private void doBundleDocURL(BundleDocURL annotation) {
+ add(Constants.BUNDLE_DOCURL, annotation.value());
+}
+
/*
+ * Bundle-License header
+ */
+private void doLicense(BundleLicense annotation) {
+ StringBuilder sb = new StringBuilder(annotation.name());
+ if (!annotation.description().equals(""))
+ sb.append(";description='").append(annotation.description().replaceAll("'", "\\'")).append("'");
+ if (!annotation.link().equals(""))
+ sb.append(";link='").append(annotation.link().replaceAll("'", "\\'")).append("'");
+ add(Constants.BUNDLE_LICENSE, sb.toString());
+}
+
Works as private package but will only include the packages when they are imported. When this header is used, bnd will recursively add packages that match the patterns until there are no more additions
+ +If the Bundle-Name is not set, it will default to the Bundle-SymbolicName.
+ + //
+ // Use the same name for the bundle name as BSN when
+ // the bundle name is not set
+ //
+ if (main.getValue(BUNDLE_NAME) == null) {
+ main.putValue(BUNDLE_NAME, bsn);
+ }
+
/*
+ * Bundle-NativeCode ::= nativecode ( ',' nativecode )* ( ’,’ optional) ?
+ * nativecode ::= path ( ';' path )* // See 1.4.2 ( ';' parameter )+
+ * optional ::= ’*’
+ */
+public void verifyNative() {
+ String nc = get(Constants.BUNDLE_NATIVECODE);
+ doNative(nc);
+}
+
+public void doNative(String nc) {
+ if (nc != null) {
+ QuotedTokenizer qt = new QuotedTokenizer(nc, ",;=", false);
+ char del;
+ do {
+ do {
+ String name = qt.nextToken();
+ if (name == null) {
+ error("Can not parse name from bundle native code header: " + nc);
+ return;
+ }
+ del = qt.getSeparator();
+ if (del == ';') {
+ if (dot != null && !dot.exists(name)) {
+ error("Native library not found in JAR: " + name);
+ }
+ } else {
+ String value = null;
+ if (del == '=')
+ value = qt.nextToken();
+
+ String key = name.toLowerCase();
+ if (key.equals("osname")) {
+ // ...
+ } else if (key.equals("osversion")) {
+ // verify version range
+ verify(value, VERSIONRANGE);
+ } else if (key.equals("language")) {
+ verify(value, ISO639);
+ } else if (key.equals("processor")) {
+ // verify(value, PROCESSORS);
+ } else if (key.equals("selection-filter")) {
+ // verify syntax filter
+ verifyFilter(value);
+ } else if (name.equals("*") && value == null) {
+ // Wildcard must be at end.
+ if (qt.nextToken() != null)
+ error("Bundle-Native code header may only END in wildcard: nc");
+ } else {
+ warning("Unknown attribute in native code: " + name + "=" + value);
+ }
+ del = qt.getSeparator();
+ }
+ } while (del == ';');
+ } while (del == ',');
+ }
+}
+
+public boolean verifyFilter(String value) {
+ String s = validateFilter(value);
+ if (s == null)
+ return true;
+
+ error(s);
+ return false;
+}
+
+public static String validateFilter(String value) {
+ try {
+ verifyFilter(value, 0);
+ return null;
+ }
+ catch (Exception e) {
+ return "Not a valid filter: " + value + e.getMessage();
+ }
+}
+
verifyListHeader(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, EENAME, false);
+
+ final static Pattern EENAME = Pattern.compile("CDC-1\\.0/Foundation-1\\.0" + "|CDC-1\\.1/Foundation-1\\.1"
+ + "|OSGi/Minimum-1\\.[1-9]" + "|JRE-1\\.1" + "|J2SE-1\\.2" + "|J2SE-1\\.3"
+ + "|J2SE-1\\.4" + "|J2SE-1\\.5" + "|JavaSE-1\\.6" + "|JavaSE-1\\.7"
+ + "|JavaSE-1\\.8" + "|PersonalJava-1\\.1" + "|PersonalJava-1\\.2"
+ + "|CDC-1\\.0/PersonalBasis-1\\.0" + "|CDC-1\\.0/PersonalJava-1\\.0");
+ final static EE[] ees = {
+ new EE("CDC-1.0/Foundation-1.0", V1_3, V1_1),
+ new EE("CDC-1.1/Foundation-1.1", V1_3, V1_2),
+ new EE("OSGi/Minimum-1.0", V1_3, V1_1),
+ new EE("OSGi/Minimum-1.1", V1_3, V1_2),
+ new EE("JRE-1.1", V1_1, V1_1), //
+ new EE("J2SE-1.2", V1_2, V1_1), //
+ new EE("J2SE-1.3", V1_3, V1_1), //
+ new EE("J2SE-1.4", V1_3, V1_2), //
+ new EE("J2SE-1.5", V1_5, V1_5), //
+ new EE("JavaSE-1.6", V1_6, V1_6), //
+ new EE("PersonalJava-1.1", V1_1, V1_1), //
+ new EE("JavaSE-1.7", V1_7, V1_7), //
+ new EE("JavaSE-1.8", V1_8, V1_8), //
+ new EE("PersonalJava-1.1", V1_1, V1_1), //
+ new EE("PersonalJava-1.2", V1_1, V1_1), new EE("CDC-1.0/PersonalBasis-1.0", V1_3, V1_1),
+ new EE("CDC-1.0/PersonalJava-1.0", V1_3, V1_1), new EE("CDC-1.1/PersonalBasis-1.1", V1_3, V1_2),
+ new EE("CDC-1.1/PersonalJava-1.1", V1_3, V1_2)
+ };
+
The Bundle-SymbolicName header can be set by the user. The default is the name of the main bnd file, or if the main bnd file is called bnd.bnd, it will be the name of the directory of the bnd file. An interesting variable is ${project} that will be set to this default name.
+ + private void verifySymbolicName() {
+ Parameters bsn = parseHeader(main.get(Analyzer.BUNDLE_SYMBOLICNAME));
+ if (!bsn.isEmpty()) {
+ if (bsn.size() > 1)
+ error("More than one BSN specified " + bsn);
+
+ String name = bsn.keySet().iterator().next();
+ if (!isBsn(name)) {
+ error("Symbolic Name has invalid format: " + name);
+ }
+ }
+}
+
+ /**
+ * @param name
+ * @return
+ */
+public static boolean isBsn(String name) {
+ return SYMBOLICNAME.matcher(name).matches();
+}
+
+
+public final static String SYMBOLICNAME_STRING = "[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*";
+
The version of the bundle. If no such header is provided, a version of 0 will be set.
+ + verifyHeader(Constants.BUNDLE_VERSION, VERSION, true);
+ public final static Pattern VERSION = Pattern.compile(VERSION_STRING);
+ public final static String VERSION_STRING = "[0-9]{1,9}(\\.[0-9]{1,9}(\\.[0-9]{1,9}(\\.[0-9A-Za-z_-]+)?)?)?";
+
+
+ /**
+ * Intercept the call to analyze and cleanup versions after we have analyzed
+ * the setup. We do not want to cleanup if we are going to verify.
+ */
+
+@Override
+public void analyze() throws Exception {
+ super.analyze();
+ cleanupVersion(getImports(), null);
+ cleanupVersion(getExports(), getVersion());
+ String version = getProperty(BUNDLE_VERSION);
+ if (version != null) {
+ version = cleanupVersion(version);
+ if (version.endsWith(".SNAPSHOT")) {
+ version = version.replaceAll("SNAPSHOT$", getProperty(SNAPSHOT, "SNAPSHOT"));
+ }
+ setProperty(BUNDLE_VERSION, version);
+ }
+}
+
+
+ if (main.getValue(BUNDLE_VERSION) == null)
+ main.putValue(BUNDLE_VERSION, "0");
+
/**
+ * Answer extra packages. In this case we implement conditional package. Any
+ */
+@Override
+protected Jar getExtra() throws Exception {
+ Parameters conditionals = getParameters(CONDITIONAL_PACKAGE);
+ conditionals.putAll(getParameters(CONDITIONALPACKAGE));
+ if (conditionals.isEmpty())
+ return null;
+ trace("do Conditional Package %s", conditionals);
+ Instructions instructions = new Instructions(conditionals);
+
+ Collection<PackageRef> referred = instructions.select(getReferred().keySet(), false);
+ referred.removeAll(getContained().keySet());
+
+ Jar jar = new Jar("conditional-import");
+ addClose(jar);
+ for (PackageRef pref : referred) {
+ for (Jar cpe : getClasspath()) {
+ Map<String,Resource> map = cpe.getDirectories().get(pref.getPath());
+ if (map != null) {
+ copy(jar, cpe, pref.getPath(), false);
+ // Now use copy so that bnd.info is processed, next line
+ // should be
+ // removed in the future TODO
+ // jar.addDirectory(map, false);
+ break;
+ }
+ }
+ }
+ if (jar.getDirectories().size() == 0) {
+ trace("extra dirs %s", jar.getDirectories());
+ return null;
+ }
+ return jar;
+}
+
if (!noExtraHeaders) {
+ main.putValue(CREATED_BY, System.getProperty("java.version") + " (" + System.getProperty("java.vendor")
+ + ")");
+ main.putValue(TOOL, "Bnd-" + getBndVersion());
+ main.putValue(BND_LASTMODIFIED, "" + System.currentTimeMillis());
+ }
+
/**
+ * <pre>
+ * DynamicImport-Package ::= dynamic-description
+ * ( ',' dynamic-description )*
+ *
+ * dynamic-description::= wildcard-names ( ';' parameter )*
+ * wildcard-names ::= wildcard-name ( ';' wildcard-name )*
+ * wildcard-name ::= package-name
+ * | ( package-name '.*' ) // See 1.4.2
+ * | '*'
+ * </pre>
+ */
+private void verifyDynamicImportPackage() {
+ verifyListHeader(Constants.DYNAMICIMPORT_PACKAGE, WILDCARDPACKAGE, true);
+ String dynamicImportPackage = get(Constants.DYNAMICIMPORT_PACKAGE);
+ if (dynamicImportPackage == null)
+ return;
+
+ Parameters map = main.getDynamicImportPackage();
+ for (String name : map.keySet()) {
+ name = name.trim();
+ if (!verify(name, WILDCARDPACKAGE))
+ error(Constants.DYNAMICIMPORT_PACKAGE + " header contains an invalid package name: " + name);
+
+ Map<String,String> sub = map.get(name);
+ if (r3 && sub.size() != 0) {
+ error("DynamicPackage-Import has attributes on import: " + name
+ + ". This is however, an <=R3 bundle and attributes on this header were introduced in R4. ");
+ }
+ }
+}
+
The bnd definition allows the specification to be done using ‘‘patterns’’, a modified regular expression. All patterns in the definition are matched against every package on the [ class path][#CLASSPATH ]. If the pattern is a negating pattern (starts with !) and it is matched, then the package is completely excluded. Normal patterns cause the package to be included in the resulting bundle. Patterns can include both directives and attributes, these items will be copied to the output. The list is ordered, earlier patterns take effect before later patterns. The following examples copies everything on the class path except for packages starting with com
. If the source packages have an associated version (from their manifest of packageinfo file), then this version is automatically added to the clauses.
Export-Package= !com.*, *
+ +Exports are automatically imported. This features can be disabled with a special directive on the export instruction: -noimport:=true
. For example:
Export-Package= com.acme.impl.*;-noimport:=true, *
+ +Bnd will automatically calculate the uses:
directive. This directive is used by the OSGi framework to create a consistent class space for a bundle. The Export-Package statement allows this directive to be overridden on a package basis by specifying the directive in an Export-Package instruction.
Export-package = com.acme.impl.*;uses=”my.special.import”
+ +However, in certain cases it is necessary to augment the uses clause. It is therefore possible to use the special name <<USES>>
in the clause. Bnd will replace this special name with the calculated uses set. Bnd will remove any extraneous commas when the <<USES>>
is empty.
Export-package = com.acme.impl.*;uses:=”my.special.import,«USES»”
+ +Directives that are not part of the OSGi specification will give a warning unless they are prefixed with a ‘x-‘.
+ + //
+ // EXPORTS
+ //
+ {
+ Set<Instruction> unused = Create.set();
+
+ Instructions filter = new Instructions(getExportPackage());
+ filter.append(getExportContents());
+
+ exports = filter(filter, contained, unused);
+
+ if (!unused.isEmpty()) {
+ warning("Unused " + Constants.EXPORT_PACKAGE + " instructions: %s ", unused);
+ }
+
+ // See what information we can find to augment the
+ // exports. I.e. look on the classpath
+ augmentExports(exports);
+ }
+
+ /**
+ * Check if the given resource is in scope of this bundle. That is, it
+ * checks if the Include-Resource includes this resource or if it is a class
+ * file it is on the class path and the Export-Package or Private-Package
+ * include this resource.
+ *
+ * @param f
+ * @return
+ */
+public boolean isInScope(Collection<File> resources) throws Exception {
+ Parameters clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
+ clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
+ clauses.putAll(parseHeader(getProperty(Constants.PRIVATEPACKAGE)));
+ if (isTrue(getProperty(Constants.UNDERTEST))) {
+ clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES, "test;presence:=optional")));
+ }
+
+ Collection<String> ir = getIncludedResourcePrefixes();
+
+ Instructions instructions = new Instructions(clauses);
+
+ for (File r : resources) {
+ String cpEntry = getClasspathEntrySuffix(r);
+
+ if (cpEntry != null) {
+
+ if (cpEntry.equals("")) // Meaning we actually have a CPE
+ return true;
+
+ String pack = Descriptors.getPackage(cpEntry);
+ Instruction i = matches(instructions, pack, null, r.getName());
+ if (i != null)
+ return !i.isNegated();
+ }
+
+ // Check if this resource starts with one of the I-C header
+ // paths.
+ String path = r.getAbsolutePath();
+ for (String p : ir) {
+ if (path.startsWith(p))
+ return true;
+ }
+ }
+ return false;
+}
+
+ /**
+ * Verify that the exports only use versions.
+ */
+private void verifyExports() {
+ if (isStrict()) {
+ Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.EXPORT_PACKAGE));
+ Set<String> noexports = new HashSet<String>();
+
+ for (Entry<String,Attrs> e : map.entrySet()) {
+
+ String version = e.getValue().get(Constants.VERSION_ATTRIBUTE);
+ if (version == null) {
+ noexports.add(e.getKey());
+ } else {
+ if (!VERSION.matcher(version).matches()) {
+ Location location;
+ if (VERSIONRANGE.matcher(version).matches()) {
+ location = error(
+ "Export Package %s version is a range: %s; Exports do not allow for ranges.",
+ e.getKey(), version).location();
+ } else {
+ location = error("Export Package %s version has invalid syntax: %s", e.getKey(), version)
+ .location();
+ }
+ location.header = Constants.EXPORT_PACKAGE;
+ location.context = e.getKey();
+ }
+ }
+
+ if (e.getValue().containsKey(Constants.SPECIFICATION_VERSION)) {
+ Location location = error(
+ "Export Package %s uses deprecated specification-version instead of version", e.getKey())
+ .location();
+ location.header = Constants.EXPORT_PACKAGE;
+ location.context = e.getKey();
+ }
+
+ String mandatory = e.getValue().get(Constants.MANDATORY_DIRECTIVE);
+ if (mandatory != null) {
+ Set<String> missing = new HashSet<String>(split(mandatory));
+ missing.removeAll(e.getValue().keySet());
+ if (!missing.isEmpty()) {
+ Location location = error("Export Package %s misses mandatory attribute: %s", e.getKey(),
+ missing).location();
+ location.header = Constants.EXPORT_PACKAGE;
+ location.context = e.getKey();
+ }
+ }
+ }
+
+ if (!noexports.isEmpty()) {
+ Location location = error("Export Package clauses without version range: %s", noexports).location();
+ location.header = Constants.EXPORT_PACKAGE;
+ }
+ }
+}
+
The Fragment-Host manifest header links the fragment to its potential hosts. A fragment bundle is loaded in the +same class loader as the host that it will be attached to in runtime. When a fragment is attached to its host, +then some headers are merged. One of those headers is the Import Package header.
+ +bnd will calculate the references without taking the host into account. If the fragment uses packages from the host, +quite likely, then these would result in imports. For this reason, bnd will subtract any package that can be found +in the host from the import.
+ +The Import-Package
header lists the packages that are required by the contained packages. The default for this header is *
, resulting in importing all referred packages. This header therefore rarely has to be specified. However, in certain cases there is an unwanted import. The import is caused by code that the author knows can never be reached. This import can be removed by using a negating pattern. A pattern is inserted in the import as an extra import when it contains no wildcards and there is no referral to that package. This can be used to add an import statement for a package that is not referred to by your code but is still needed, for example, because the class is loaded by name.
For example:
+ +Import-Package: !org.apache.commons.log4j, com.acme.*,\
+ com.foo.extra
+
During processing, bnd will attempt to find the exported version of imported packages. If no version or version range is specified on the import instruction, the exported version will then be used though the micro part and the qualifier are dropped. That is, when the exporter is 1.2.3.build123
, then the import version will be 1.2. If a specific version (range) is specified, this will override any found version. This default an be overridden with the -versionpolicy instruction.
If an explicit version is given, then ${@}
can be used to substitute the found version in a range. In those cases, the range macro can be very useful to calculate ranges and drop specific parts of the version. For example:
Import-Package: org.osgi.framework;version="[1.3,2.0)"
+Import-Package: org.osgi.framework;version="${@}"
+Import-Package: org.osgi.framework;version="${range;[==,=+);${@}}"
+
You can reference the Bundle-SymbolicName
and Bundle-Version
of the exporter on the classpath by using the ${@bundlesymbolicname}
and ${@bundleversion}
values. In those cases, the range macro can be very useful to calculate ranges and drop specific parts of the bundle version. For example:
Import-Package: org.eclipse.jdt.ui;bundle-symbolic-name="${@bundlesymbolicname}";\
+ bundle-version="${range;[==,+);${@bundleversion}}"
+
Packages with directive resolution:=dynamic
will be removed from Import-Package
and added to the DynamicImport-Package
header after being processed like any other Import-Package
entry. For example:
Import-Package: org.slf4j.*;resolution:=dynamic, *
+
If an imported package uses mandatory attributes, then bnd will attempt to add those attributes to the import statement. However, in certain (bizarre!) cases this is not wanted. It is therefore possible to remove an attribute from the import clause. This is done with the -remove-attribute
directive or by setting the value of an attribute to !
. The parameter of the -remove-attribute
directive is an instruction and can use the standard options with !
, *
, ?
, etc.
Import-Package: org.eclipse.core.runtime;-remove-attribute:="common",*
+
Or
+ +Import-Package: org.eclipse.core.runtime;common=!,*
+
Directives that are not part of the OSGi specification will give a warning unless they are prefixed with x-
.
See -includeresource
+ + +/**
+ * Verify the Meta-Persistence header
+ *
+ * @throws Exception
+ */
+
+public void verifyMetaPersistence() throws Exception {
+ List<String> list = new ArrayList<String>();
+ String mp = dot.getManifest().getMainAttributes().getValue(META_PERSISTENCE);
+ for (String location : OSGiHeader.parseHeader(mp).keySet()) {
+ String[] parts = location.split("!/");
+
+ Resource resource = dot.getResource(parts[0]);
+ if (resource == null)
+ list.add(location);
+ else {
+ if (parts.length > 1) {
+ Jar jar = new Jar("", resource.openInputStream());
+ try {
+ resource = jar.getResource(parts[1]);
+ if (resource == null)
+ list.add(location);
+ }
+ catch (Exception e) {
+ list.add(location);
+ }
+ finally {
+ jar.close();
+ }
+ }
+ }
+ }
+ if (list.isEmpty())
+ return;
+
+ error(Constants.META_PERSISTENCE + " refers to resources not in the bundle: %s", list).header(Constants.META_PERSISTENCE);
+}
+
The method of inclusion is identical to the Export-Package header, the only difference is, is that these packages are not exported. This header will be copied to the manifest. If a package is selected by the export and private package headers, then the export takes precedence.
+ +Private-Package= com.*
+ +Bnd traverse the packages on the classpath and copies them to the output based on the instructions given by the Export-Package and Private-Package headers. This opens up for the possibility that there are multiple packages with the same name on the class path. It is better to avoid this situation because it means there is no cohesive definition of the package and it is just, eh, messy. However, there are valid cases that packages should be merged from different sources. For example, when a standard package needs to be merged with implementation code like the osgi packages sometimes (unfortunately) do. Without any extra instructions, bnd will merge multiple packages where the last one wins if the packages contain duplicate resources, but it will give a warning to notify the unwanted case of split packages.
+ +The -split-package:
directive on the Export-Package/Private-Package clause allows fine grained control over what should be done with split packages. The following values are architected:
+ | + | Merge split packages but do not add resources that come later in the classpath. That is, the first resource wins. This is the default, although the default will generate a warning | ++ | |
+ | + | Merge split packages but overwrite resources that come earlier in the classpath. That is, the last resource wins. | ++ | |
+ | first |
+ + | Do not merge, only use the first package found | ++ |
+ | error |
+ + | Generate an error when a split package is detected | ++ |
For example:
+ +Private-Package: test.pack;-split-package:=merge-first
+ +private void doExpand(Jar dot) {
+
+ // Build an index of the class path that we can then
+ // use destructively
+ MultiMap<String,Jar> packages = new MultiMap<String,Jar>();
+ for (Jar srce : getClasspath()) {
+ dot.updateModified(srce.lastModified, srce + " (" + srce.lastModifiedReason + ")");
+ for (Entry<String,Map<String,Resource>> e : srce.getDirectories().entrySet()) {
+ if (e.getValue() != null)
+ packages.add(e.getKey(), srce);
+ }
+ }
+
+ Parameters privatePackages = getPrivatePackage();
+ if (isTrue(getProperty(Constants.UNDERTEST))) {
+ String h = getProperty(Constants.TESTPACKAGES, "test;presence:=optional");
+ privatePackages.putAll(parseHeader(h));
+ }
+
+ if (!privatePackages.isEmpty()) {
+ Instructions privateFilter = new Instructions(privatePackages);
+ Set<Instruction> unused = doExpand(dot, packages, privateFilter);
+
+ if (!unused.isEmpty()) {
+ warning("Unused " + Constants.PRIVATE_PACKAGE + " instructions, no such package(s) on the class path: %s", unused);
+ }
+ }
+
+ Parameters exportedPackage = getExportPackage();
+ if (!exportedPackage.isEmpty()) {
+ Instructions exportedFilter = new Instructions(exportedPackage);
+
+ // We ignore unused instructions for exports, they should show
+ // up as errors during analysis. Otherwise any overlapping
+ // packages with the private packages should show up as
+ // unused
+
+ doExpand(dot, packages, exportedFilter);
+ }
+}
+
+
+
+
+
+
+ /**
+ * Allow any local initialization by subclasses before we build.
+ */
+public void init() throws Exception {
+ begin();
+ doRequireBnd();
+
+ // Check if we have sensible setup
+
+ if (getClasspath().size() == 0
+ && (getProperty(EXPORT_PACKAGE) != null || getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null || getProperty(PRIVATEPACKAGE) != null))
+ warning("Classpath is empty. " + Constants.PRIVATE_PACKAGE + " (-privatepackage) and " + EXPORT_PACKAGE + " can only expand from the classpath when there is one");
+
+}
+
+
+
+ /**
+ * Check if the given resource is in scope of this bundle. That is, it
+ * checks if the Include-Resource includes this resource or if it is a class
+ * file it is on the class path and the Export-Package or Private-Package
+ * include this resource.
+ *
+ * @param f
+ * @return
+ */
+public boolean isInScope(Collection<File> resources) throws Exception {
+ Parameters clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
+ clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
+ clauses.putAll(parseHeader(getProperty(Constants.PRIVATEPACKAGE)));
+ if (isTrue(getProperty(Constants.UNDERTEST))) {
+ clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES, "test;presence:=optional")));
+ }
+
+ Collection<String> ir = getIncludedResourcePrefixes();
+
+ Instructions instructions = new Instructions(clauses);
+
+ for (File r : resources) {
+ String cpEntry = getClasspathEntrySuffix(r);
+
+ if (cpEntry != null) {
+
+ if (cpEntry.equals("")) // Meaning we actually have a CPE
+ return true;
+
+ String pack = Descriptors.getPackage(cpEntry);
+ Instruction i = matches(instructions, pack, null, r.getName());
+ if (i != null)
+ return !i.isNegated();
+ }
+
+ // Check if this resource starts with one of the I-C header
+ // paths.
+ String path = r.getAbsolutePath();
+ for (String p : ir) {
+ if (path.startsWith(p))
+ return true;
+ }
+ }
+ return false;
+}
+
Parameters capabilities = new Parameters(annotationHeaders.getHeader(PROVIDE_CAPABILITY));
+
+
+ /*
+ * Provide-Capability header
+ */
+private void doProvideCapability(ProvideCapability annotation) {
+ StringBuilder sb = new StringBuilder(annotation.ns());
+ if (annotation.name() != null)
+ sb.append(";").append(annotation.ns()).append("='").append(annotation.name()).append("'");
+ if (annotation.uses() != null)
+ sb.append(";").append("uses:='").append(Strings.join(",", annotation.uses())).append("'");
+ if (annotation.mandatory() != null)
+ sb.append(";").append("mandatory:='").append(Strings.join(",", annotation.mandatory())).append("'");
+ if (annotation.version() != null)
+ sb.append(";").append("version:Version='").append(annotation.version()).append("'");
+ if (annotation.value() != null)
+ sb.append(";").append(annotation.value());
+ if (annotation.effective() != null)
+ sb.append(";effective:='").append(annotation.effective()).append("'");
+
+ add(Constants.PROVIDE_CAPABILITY, sb.toString());
+}
+
+
+
+
+ package aQute.bnd.annotation.headers;
+
+ import java.lang.annotation.*;
+
+ /**
+ * Define a Provide Capability clause in the manifest.
+ * <p>
+ * Since this annotation can only be applied once, it is possible to create an annotation
+ * that models a specific capability. For example:
+ * <pre>
+ * interface Webserver {
+ * @ProvideCapability(ns="osgi.extender", name="aQute.webserver", version = "${@version}")
+ * @interface Provide {}
+ *
+ * @RequireCapability(ns="osgi.extender", filter="(&(osgi.extender=aQute.webserver)${frange;${@version}})")
+ * @interface Require {}
+ * }
+ *
+ * Webserver.@Provide
+ * public class MyWebserver {
+ * }
+ * </pre>
+ *
+ */
+ @Retention(RetentionPolicy.CLASS)
+ @Target({
+ ElementType.ANNOTATION_TYPE, ElementType.TYPE
+ })
+ public @interface ProvideCapability {
+ /**
+ * Appended at the end of the clause (after a ';'). Can be used to add
+ * additional attributes and directives.
+ */
+ String value() default "";
+
+ /**
+ * The capability namespace. For example: {@code osgi.contract}.
+ */
+ String ns();
+
+ /**
+ * The name of the capability. If this is set, a property will be added as
+ * {ns}={name}. This is the custom pattern for OSGi namespaces. Leaving this
+ * unfilled, requires the {@link #value()} to be used to specify the name of
+ * the capability, if needed. For example {@code aQute.sse}.
+ */
+ String name() default "";
+
+ /**
+ * The version of the capability. This must be a valid OSGi version.
+ */
+ String version() default "";
+
+ /**
+ * Effective time. Specifies the time a capability is available, either
+ * resolve (default) or another name. The OSGi framework resolver only
+ * considers Capabilities without an effective directive or
+ * effective:=resolve. Capabilities with other values for the effective
+ * directive can be considered by an external agent.
+ */
+ String effective() default "resolve";
+
+ /**
+ * The uses directive lists package names that are used by this Capability.
+ * This information is intended to be used for <em>uses constraints</em>,
+ */
+ String[] uses() default {};
+
+ /**
+ * Mandatory attributes. Forces the resolver to only satisfy filters that
+ * refer to all listed attributes.
+ */
+ String[] mandatory() default {};
+ }
+
+
+
+ verifyDirectives(Constants.PROVIDE_CAPABILITY, "effective:|uses:", null, null);
+
+
+
+
+ private void verifyCapabilities() {
+ Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.PROVIDE_CAPABILITY));
+ for (String key : map.keySet()) {
+ Attrs attrs = map.get(key);
+ verify(attrs, "cardinality:", CARDINALITY_PATTERN, false, "Requirement %s cardinality not correct", key);
+ verify(attrs, "resolution:", RESOLUTION_PATTERN, false, "Requirement %s resolution not correct", key);
+
+ if (key.equals("osgi.extender")) {
+ verify(attrs, "osgi.extender", SYMBOLICNAME, true,
+ "Extender %s must always have the osgi.extender attribute set", key);
+ verify(attrs, "version", VERSION, true, "Extender %s must always have a version", key);
+ } else if (key.equals("osgi.serviceloader")) {
+ verify(attrs, "register:", PACKAGEPATTERN, false,
+ "Service Loader extender register: directive not a fully qualified Java name");
+ } else if (key.equals("osgi.contract")) {
+ verify(attrs, "osgi.contract", SYMBOLICNAME, true,
+ "Contracts %s must always have the osgi.contract attribute set", key);
+
+ } else if (key.equals("osgi.service")) {
+ verify(attrs, "objectClass", MULTIPACKAGEPATTERN, true,
+ "osgi.service %s must have the objectClass attribute set", key);
+
+ } else if (key.equals("osgi.ee")) {
+ // TODO
+ } else if (key.startsWith("osgi.wiring.") || key.startsWith("osgi.identity")) {
+ error("osgi.wiring.* namespaces must not be specified with generic requirements/capabilities");
+ }
+
+ verifyAttrs(attrs);
+
+ if (attrs.containsKey("filter:"))
+ error("filter: directive is intended for Requirements, not Capability %s", key);
+ if (attrs.containsKey("cardinality:"))
+ error("cardinality: directive is intended for Requirements, not Capability %s", key);
+ if (attrs.containsKey("resolution:"))
+ error("resolution: directive is intended for Requirements, not Capability %s", key);
+ }
+}
+
verifyDirectives(Constants.REQUIRE_BUNDLE, "visibility:|resolution:", SYMBOLICNAME, "bsn");
+
+
+
+ //
+ // If there is a Require bundle, all bets are off and
+ // we cannot verify anything
+ //
+
+ if (domain.getRequireBundle().isEmpty() && domain.get("ExtensionBundle-Activator") == null
+ && (domain.getFragmentHost()== null || domain.getFragmentHost().getKey().equals("system.bundle"))) {
+
+ if (!unresolvedReferences.isEmpty()) {
+ // Now we want to know the
+ // classes that are the culprits
+ Set<String> culprits = new HashSet<String>();
+ for (Clazz clazz : analyzer.getClassspace().values()) {
+ if (hasOverlap(unresolvedReferences, clazz.getReferred()))
+ culprits.add(clazz.getAbsolutePath());
+ }
+
+ if (analyzer instanceof Builder)
+ warning("Unresolved references to %s by class(es) %s on the " + Constants.BUNDLE_CLASSPATH + ": %s",
+ unresolvedReferences, culprits, analyzer.getBundleClasspath().keySet());
+ else
+ error("Unresolved references to %s by class(es) %s on the " + Constants.BUNDLE_CLASSPATH + ": %s",
+ unresolvedReferences, culprits, analyzer.getBundleClasspath().keySet());
+ return;
+ }
+ } else if (isPedantic())
+ warning("Use of " + Constants.REQUIRE_BUNDLE + ", ExtensionBundle-Activator, or a system bundle fragment makes it impossible to verify unresolved references");
+
Parameters requirements = new Parameters(annotationHeaders.getHeader(REQUIRE_CAPABILITY));
+ Parameters capabilities = new Parameters(annotationHeaders.getHeader(PROVIDE_CAPABILITY));
+
+ //
+ // Do any contracts contracts
+ //
+ contracts.addToRequirements(requirements);
+
+ //
+ // We want to add the minimum EE as a requirement
+ // based on the class version
+ //
+
+ if (!isTrue(getProperty(NOEE)) //
+ && !ees.isEmpty() // no use otherwise
+ && since(About._2_3) // we want people to not have to
+ // automatically add it
+ && !requirements.containsKey(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE) // and
+ // it
+ // should
+ // not
+ // be
+ // there
+ // already
+ ) {
+
+ JAVA highest = ees.last();
+ Attrs attrs = new Attrs();
+
+ String filter = doEEProfiles(highest);
+
+ attrs.put(Constants.FILTER_DIRECTIVE, filter);
+
+ //
+ // Java 1.8 introduced profiles.
+ // If -eeprofile= auto | (<profile>="...")+ is set then
+ // we add a
+
+ requirements.add(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE, attrs);
+ }
+
+ if (!requirements.isEmpty())
+ main.putValue(REQUIRE_CAPABILITY, requirements.toString());
+
+
+ /*
+ * Require-Capability header
+ */
+private void doRequireCapability(RequireCapability annotation) {
+ StringBuilder sb = new StringBuilder(annotation.ns());
+ if (annotation.filter() != null)
+ sb.append(";filter:='").append(annotation.filter()).append("'");
+ if (annotation.effective() != null)
+ sb.append(";effective:='").append(annotation.effective()).append("'");
+ if (annotation.resolution() != null)
+ sb.append(";resolution:='").append(annotation.resolution()).append("'");
+
+ if (annotation.value() != null)
+ sb.append(";").append(annotation.value());
+
+ add(Constants.REQUIRE_CAPABILITY, sb.toString());
+}
+
+
+package aQute.bnd.annotation.headers;
+
+ import java.lang.annotation.*;
+
+ /**
+ * The Bundle’s Require-Capability header
+ *
+ * {@link About}
+ */
+ @Retention(RetentionPolicy.CLASS)
+ @Target({
+ ElementType.ANNOTATION_TYPE, ElementType.TYPE
+ })
+ public @interface RequireCapability {
+ String value() default "";
+
+ /**
+ * The capability namespace. For example: {@code osgi.contract}.
+ */
+ String ns();
+
+ /**
+ * Specifies the time a Requirement is considered, either 'resolve'
+ * (default) or another name. The OSGi framework resolver only considers
+ * Requirements without an effective directive or effective:=resolve. Other
+ * Requirements can be considered by an external agent. Additional names for
+ * the effective directive should be registered with the OSGi Alliance. See
+ * <a href="https://www.osgi.org/developer/specifications/reference/">OSGi Reference
+ * Page</a>
+ */
+ String effective() default "resolve";
+
+ /**
+ * A filter expression that is asserted on the Capabilities belonging to the
+ * given namespace. The matching of the filter against the Capability is
+ * done on one Capability at a time. A filter like {@code (&(a=1)(b=2))}
+ * matches only a Capability that specifies both attributes at the required
+ * value, not two capabilties that each specify one of the attributes
+ * correctly. A filter is optional, if no filter directive is specified the
+ * Requirement always matches.
+ */
+ String filter();
+
+ /**
+ * A mandatory Requirement forbids the bundle to resolve when the
+ * Requirement is not satisfied; an optional Requirement allows a bundle to
+ * resolve even if the Requirement is not satisfied. No wirings are created
+ * when this Requirement cannot be resolved, this can result in Class Not
+ * Found Exceptions when the bundle attempts to use a package that was not
+ * resolved because it was optional.
+ */
+ Resolution resolution() default Resolution.mandatory;
+
+ }
+
+
+ private void verifyRequirements() {
+ Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.REQUIRE_CAPABILITY));
+ for (String key : map.keySet()) {
+ Attrs attrs = map.get(key);
+ verify(attrs, "filter:", FILTERPATTERN, false, "Requirement %s filter not correct", key);
+
+ String filter = attrs.get("filter:");
+ if (filter != null) {
+ String verify = new Filter(filter).verify();
+ if (verify != null)
+ error("Invalid filter syntax in requirement %s=%s. Reason %s", key, attrs, verify);
+ }
+ verify(attrs, "cardinality:", CARDINALITY_PATTERN, false, "Requirement %s cardinality not correct", key);
+ verify(attrs, "resolution:", RESOLUTION_PATTERN, false, "Requirement %s resolution not correct", key);
+
+ if (key.equals("osgi.extender")) {
+ // No requirements on extender
+ } else if (key.equals("osgi.serviceloader")) {
+ verify(attrs, "register:", PACKAGEPATTERN, false,
+ "Service Loader extender register: directive not a fully qualified Java name");
+ } else if (key.equals("osgi.contract")) {
+
+ } else if (key.equals("osgi.service")) {
+
+ } else if (key.equals("osgi.ee")) {
+
+ } else if (key.startsWith("osgi.wiring.") || key.startsWith("osgi.identity")) {
+ error("osgi.wiring.* namespaces must not be specified with generic requirements/capabilities");
+ }
+
+ verifyAttrs(attrs);
+
+ if (attrs.containsKey("mandatory:"))
+ error("mandatory: directive is intended for Capabilities, not Requirement %s", key);
+
+ if (attrs.containsKey("uses:"))
+ error("uses: directive is intended for Capabilities, not Requirement %s", key);
+ }
+}
+
/**
+ * Analyze the class space for any classes that have an OSGi annotation for DS.
+ */
+public class DSAnnotations implements AnalyzerPlugin {
+
+ public boolean analyzeJar(Analyzer analyzer) throws Exception {
+ Parameters header = OSGiHeader.parseHeader(analyzer.getProperty(Constants.DSANNOTATIONS));
+ if (header.size() == 0)
+ return false;
+
+ Instructions instructions = new Instructions(header);
+ Collection<Clazz> list = analyzer.getClassspace().values();
+ String sc = analyzer.getProperty(Constants.SERVICE_COMPONENT);
+ List<String> names = new ArrayList<String>();
+ if (sc != null && sc.trim().length() > 0)
+ names.add(sc);
+
+ for (Clazz c: list) {
+ for (Instruction instruction : instructions.keySet()) {
+
+ if (instruction.matches(c.getFQN())) {
+ if (instruction.isNegated())
+ break;
+ ComponentDef definition = AnnotationReader.getDefinition(c, analyzer);
+ if (definition != null) {
+ definition.sortReferences();
+ definition.prepare(analyzer);
+ String name = "OSGI-INF/" + definition.name + ".xml";
+ names.add(name);
+ analyzer.getJar().putResource(name, new TagResource(definition.getTag()));
+ }
+ }
+ }
+ }
+ sc = Processor.append(names.toArray(new String[names.size()]));
+ analyzer.setProperty(Constants.SERVICE_COMPONENT, sc);
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "DSAnnotations";
+ }
+}
+
+ package aQute.bnd.make.component;
+
+ import java.io.*;
+ import java.util.*;
+ import java.util.Map.Entry;
+
+ import aQute.bnd.annotation.component.*;
+ import aQute.bnd.component.*;
+ import aQute.bnd.header.*;
+ import aQute.bnd.make.metatype.*;
+ import aQute.bnd.osgi.*;
+ import aQute.bnd.osgi.Clazz.QUERY;
+ import aQute.bnd.osgi.Descriptors.TypeRef;
+ import aQute.bnd.service.*;
+ import aQute.lib.tag.*;
+
+ /**
+ * This class is an analyzer plugin. It looks at the properties and tries to
+ * find out if the Service-Component header contains the bnd shortut syntax. If
+ * not, the header is copied to the output, if it does, an XML file is created
+ * and added to the JAR and the header is modified appropriately.
+ */
+ public class ServiceComponent implements AnalyzerPlugin {
+
+ public boolean analyzeJar(Analyzer analyzer) throws Exception {
+
+ ComponentMaker m = new ComponentMaker(analyzer);
+
+ Map<String,Map<String,String>> l = m.doServiceComponent();
+
+ analyzer.setProperty(Constants.SERVICE_COMPONENT, Processor.printClauses(l));
+
+ analyzer.getInfo(m, Constants.SERVICE_COMPONENT + ": ");
+ m.close();
+
+ return false;
+ }
+
+ private static class ComponentMaker extends Processor {
+ Analyzer analyzer;
+
+ ComponentMaker(Analyzer analyzer) {
+ super(analyzer);
+ this.analyzer = analyzer;
+ }
+
+ /**
+ * Iterate over the Service Component entries. There are two cases:
+ * <ol>
+ * <li>An XML file reference</li>
+ * <li>A FQN/wildcard with a set of attributes</li>
+ * </ol>
+ * An XML reference is immediately expanded, an FQN/wildcard is more
+ * complicated and is delegated to
+ * {@link #componentEntry(Map, String, Map)}.
+ *
+ * @throws Exception
+ */
+ Map<String,Map<String,String>> doServiceComponent() throws Exception {
+ Map<String,Map<String,String>> serviceComponents = newMap();
+ String header = getProperty(SERVICE_COMPONENT);
+ Parameters sc = parseHeader(header);
+
+ for (Entry<String,Attrs> entry : sc.entrySet()) {
+ String name = entry.getKey();
+ Map<String,String> info = entry.getValue();
+
+ try {
+ if (name.indexOf('/') >= 0 || name.endsWith(".xml")) {
+ // Normal service component, we do not process it
+ serviceComponents.put(name, EMPTY);
+ } else {
+ componentEntry(serviceComponents, name, info);
+ }
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ error("Invalid " + Constants.SERVICE_COMPONENT + " header: %s %s, throws %s", name, info, e);
+ throw e;
+ }
+ }
+ return serviceComponents;
+ }
+
+ /**
+ * Parse an entry in the Service-Component header. This header supports
+ * the following types:
+ * <ol>
+ * <li>An FQN + attributes describing a component</li>
+ * <li>A wildcard expression for finding annotated components.</li>
+ * </ol>
+ * The problem is the distinction between an FQN and a wildcard because
+ * an FQN can also be used as a wildcard. If the info specifies
+ * {@link Constants#NOANNOTATIONS} then wildcards are an error and the
+ * component must be fully described by the info. Otherwise the
+ * FQN/wildcard is expanded into a list of classes with annotations. If
+ * this list is empty, the FQN case is interpreted as a complete
+ * component definition. For the wildcard case, it is checked if any
+ * matching classes for the wildcard have been compiled for a class file
+ * format that does not support annotations, this can be a problem with
+ * JSR14 who silently ignores annotations. An error is reported in such
+ * a case.
+ *
+ * @param serviceComponents
+ * @param name
+ * @param info
+ * @throws Exception
+ * @throws IOException
+ */
+ private void componentEntry(Map<String,Map<String,String>> serviceComponents, String name,
+ Map<String,String> info) throws Exception, IOException {
+
+ boolean annotations = !Processor.isTrue(info.get(NOANNOTATIONS));
+ boolean fqn = Verifier.isFQN(name);
+
+ if (annotations) {
+
+ // Annotations possible!
+
+ Collection<Clazz> annotatedComponents = analyzer.getClasses("", QUERY.ANNOTATED.toString(),
+ Component.class.getName(), //
+ QUERY.NAMED.toString(), name //
+ );
+
+ if (fqn) {
+ if (annotatedComponents.isEmpty()) {
+
+ // No annotations, fully specified in header
+
+ createComponentResource(serviceComponents, name, info);
+ } else {
+
+ // We had a FQN so expect only one
+
+ for (Clazz c : annotatedComponents) {
+ annotated(serviceComponents, c, info);
+ }
+ }
+ } else {
+
+ // We did not have an FQN, so expect the use of wildcards
+
+ if (annotatedComponents.isEmpty())
+ checkAnnotationsFeasible(name);
+ else
+ for (Clazz c : annotatedComponents) {
+ annotated(serviceComponents, c, info);
+ }
+ }
+ } else {
+ // No annotations
+ if (fqn)
+ createComponentResource(serviceComponents, name, info);
+ else
+ error("Set to %s but entry %s is not an FQN ", NOANNOTATIONS, name);
+
+ }
+ }
+
+ /**
+ * Check if annotations are actually feasible looking at the class
+ * format. If the class format does not provide annotations then it is
+ * no use specifying annotated components.
+ *
+ * @param name
+ * @return
+ * @throws Exception
+ */
+ private Collection<Clazz> checkAnnotationsFeasible(String name) throws Exception {
+ Collection<Clazz> not = analyzer.getClasses("", QUERY.NAMED.toString(), name //
+ );
+
+ if (not.isEmpty()) {
+ if ("*".equals(name))
+ return not;
+ error("Specified %s but could not find any class matching this pattern", name);
+ }
+
+ for (Clazz c : not) {
+ if (c.getFormat().hasAnnotations())
+ return not;
+ }
+
+ warning("Wildcards are used (%s) requiring annotations to decide what is a component. Wildcard maps to classes that are compiled with java.target < 1.5. Annotations were introduced in Java 1.5",
+ name);
+
+ return not;
+ }
+
+ void annotated(Map<String,Map<String,String>> components, Clazz c, Map<String,String> info) throws Exception {
+ // Get the component definition
+ // from the annotations
+ Map<String,String> map = ComponentAnnotationReader.getDefinition(c, this);
+
+ // Pick the name, the annotation can override
+ // the name.
+ String localname = map.get(COMPONENT_NAME);
+ if (localname == null)
+ localname = c.getFQN();
+
+ // Override the component info without manifest
+ // entries. We merge the properties though.
+
+ String merged = Processor.merge(info.remove(COMPONENT_PROPERTIES), map.remove(COMPONENT_PROPERTIES));
+ if (merged != null && merged.length() > 0)
+ map.put(COMPONENT_PROPERTIES, merged);
+ map.putAll(info);
+ createComponentResource(components, localname, map);
+ }
+
+ private void createComponentResource(Map<String,Map<String,String>> components, String name,
+ Map<String,String> info) throws Exception {
+
+ // We can override the name in the parameters
+ if (info.containsKey(COMPONENT_NAME))
+ name = info.get(COMPONENT_NAME);
+
+ // Assume the impl==name, but allow override
+ String impl = name;
+ if (info.containsKey(COMPONENT_IMPLEMENTATION))
+ impl = info.get(COMPONENT_IMPLEMENTATION);
+
+ TypeRef implRef = analyzer.getTypeRefFromFQN(impl);
+ // Check if such a class exists
+ analyzer.referTo(implRef);
+
+ boolean designate = designate(name, info.get(COMPONENT_DESIGNATE), false)
+ || designate(name, info.get(COMPONENT_DESIGNATEFACTORY), true);
+
+ // If we had a designate, we want a default configuration policy of
+ // require.
+ if (designate && info.get(COMPONENT_CONFIGURATION_POLICY) == null)
+ info.put(COMPONENT_CONFIGURATION_POLICY, "require");
+
+ // We have a definition, so make an XML resources
+ Resource resource = createComponentResource(name, impl, info);
+ analyzer.getJar().putResource("OSGI-INF/" + name + ".xml", resource);
+
+ components.put("OSGI-INF/" + name + ".xml", EMPTY);
+
+ }
+
+ /**
+ * Create a Metatype and Designate record out of the given
+ * configurations.
+ *
+ * @param name
+ * @param config
+ * @throws Exception
+ */
+ private boolean designate(String name, String config, boolean factory) throws Exception {
+ if (config == null)
+ return false;
+
+ for (String c : Processor.split(config)) {
+ TypeRef ref = analyzer.getTypeRefFromFQN(c);
+ Clazz clazz = analyzer.findClass(ref);
+ if (clazz != null) {
+ analyzer.referTo(ref);
+ MetaTypeReader r = new MetaTypeReader(clazz, analyzer);
+ r.setDesignate(name, factory);
+ String rname = "OSGI-INF/metatype/" + name + ".xml";
+
+ analyzer.getJar().putResource(rname, r);
+ } else {
+ analyzer.error("Cannot find designated configuration class %s for component %s", c, name);
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Create the resource for a DS component.
+ *
+ * @param list
+ * @param name
+ * @param info
+ * @throws UnsupportedEncodingException
+ */
+ Resource createComponentResource(String name, String impl, Map<String, String> info)
+ throws Exception {
+ HeaderReader hr = new HeaderReader(analyzer);
+ Tag tag = hr.createComponentTag(name, impl, info);
+ hr.close();
+ return new TagResource(tag);
+ }
+ }
+
+ }
+
+
+
+
+
+
+ private void verifyComponent() {
+ String serviceComponent = main.get(Constants.SERVICE_COMPONENT);
+ if (serviceComponent != null) {
+ Parameters map = parseHeader(serviceComponent);
+ for (String component : map.keySet()) {
+ if (component.indexOf("*") < 0 && !dot.exists(component)) {
+ error(Constants.SERVICE_COMPONENT + " entry can not be located in JAR: " + component);
+ } else {
+ // validate component ...
+ }
+ }
+ }
+}
+
private void checkForTesting(Project project, Properties properties) throws Exception {
+
+ //
+ // Only run junit when we have a test src directory
+ //
+
+ boolean junit = project.getTestSrc().isDirectory() && !Processor.isTrue(project.getProperty(Constants.NOJUNIT));
+ boolean junitOsgi = project.getProperties().getProperty(Constants.TESTCASES) != null
+ && !Processor.isTrue(project.getProperty(Constants.NOJUNITOSGI));
+
+ if (junit)
+ properties.setProperty("project.junit", "true");
+ if (junitOsgi)
+ properties.setProperty("project.osgi.junit", "true");
+}
+
+public void test(List<String> tests) throws Exception {
+
+ String testcases = getProperties().getProperty(Constants.TESTCASES);
+ if (testcases == null) {
+ warning("No %s set", Constants.TESTCASES);
+ return;
+ }
+ clear();
+
+ ProjectTester tester = getProjectTester();
+ if ( tests != null) {
+ trace("Adding tests %s", tests);
+ for ( String test : tests) {
+ tester.addTest(test);
+ }
+ }
+ tester.setContinuous(isTrue(getProperty(Constants.TESTCONTINUOUS)));
+ tester.prepare();
+
+ if (!isOk()) {
+ return;
+ }
+ int errors = tester.test();
+ if (errors == 0) {
+ System.err.println("No Errors");
+ } else {
+ if (errors > 0) {
+ System.err.println(errors + " Error(s)");
+
+ } else
+ System.err.println("Error " + errors);
+ }
+}
+
+ void automatic() throws IOException {
+ String testerDir = context.getProperty(TESTER_DIR);
+ if (testerDir == null)
+ testerDir = "testdir";
+
+ final File reportDir = new File(testerDir);
+ final List<Bundle> queue = new Vector<Bundle>();
+ if (!reportDir.exists() && !reportDir.mkdirs()) {
+ throw new IOException("Could not create directory " + reportDir);
+ }
+ trace("using %s, needed creation %s", reportDir, reportDir.mkdirs());
+
+ trace("adding Bundle Listener for getting test bundle events");
+ context.addBundleListener(new SynchronousBundleListener() {
+ public void bundleChanged(BundleEvent event) {
+ if (event.getType() == BundleEvent.STARTED) {
+ checkBundle(queue, event.getBundle());
+ }
+
+ }
+ });
+
+ for (Bundle b : context.getBundles()) {
+ checkBundle(queue, b);
+ }
+
+ trace("starting queue");
+ int result = 0;
+ outer: while (active) {
+ Bundle bundle;
+ synchronized (queue) {
+ while (queue.isEmpty() && active) {
+ try {
+ queue.wait();
+ }
+ catch (InterruptedException e) {
+ trace("tests bundle queue interrupted");
+ thread.interrupt();
+ break outer;
+ }
+ }
+ }
+ try {
+ bundle = queue.remove(0);
+ trace("received bundle to test: %s", bundle.getLocation());
+ Writer report = getReportWriter(reportDir, bundle);
+ try {
+ trace("test will run");
+ result += test(bundle, (String) bundle.getHeaders().get(aQute.bnd.osgi.Constants.TESTCASES), report);
+ trace("test ran");
+ if (queue.isEmpty() && !continuous) {
+ trace("queue " + queue);
+ System.exit(result);
+ }
+ }
+ finally {
+ if (report != null)
+ report.close();
+ }
+ }
+ catch (Exception e) {
+ error("Not sure what happened anymore %s", e);
+ System.exit(254);
+ }
+ }
+}
+
+
+ void checkBundle(List<Bundle> queue, Bundle bundle) {
+ if (bundle.getState() == Bundle.ACTIVE) {
+ String testcases = (String) bundle.getHeaders().get(aQute.bnd.osgi.Constants.TESTCASES);
+ if (testcases != null) {
+ trace("found active bundle with test cases %s : %s", bundle, testcases);
+ synchronized (queue) {
+ queue.add(bundle);
+ queue.notifyAll();
+ }
+ }
+ }
+}
+
+
+
+
+ if (testcases == null) { // if ( !continuous) { // System.err.println("\nThe -testcontinuous property must be set if invoked without arguments\n"); // System.exit(255); // }
+
+ trace("automatic testing of all bundles with " + aQute.bnd.osgi.Constants.TESTCASES + " header");
+ try {
+ automatic();
+ }
+ catch (IOException e) {
+ // ignore
+ }
+
if (!noExtraHeaders) {
+ main.putValue(CREATED_BY, System.getProperty("java.version") + " (" + System.getProperty("java.vendor")
+ + ")");
+ main.putValue(TOOL, "Bnd-" + getBndVersion());
+ main.putValue(BND_LASTMODIFIED, "" + System.currentTimeMillis());
+ }
+
“If you want to teach people a new way of thinking, don't bother trying to teach them. Instead, give them a tool, the use of which will lead to new ways of thinking.” ++
― R. Buckminster Fuller +
+ bnd is the engine behind a number of popular software development + tools that support OSGi. It can be found in several maven plugins, + ant, gradle, and of course Eclipse (bndtools). + It actively seeks other build tool vendors to use bnd to improve the + quality of the generated OSGi metadata. +
bnd actually consists of 2 major parts, one part is really + good in creating a JAR with OSGi meta data based on instructions and + the information in the class files. Over time bnd has collected + hundreds of heuristics that provide for good bundles. This part is + therefore often used in build tools like maven or gradle that + already generate JARs. They mainly use the manifest generation + features of bnd. +
The other part is a IDE/build tool independent model of a + workspace with projects. This was the result of the frustration that + certain things could be done very well in Eclipse but that other + things are best done in a command line tool. Since IDEs are + incremental and very event driven, command line tools tend to run + everything from start to end. What was needed was an independent + model that could work with Eclipse, maven, ant, gradle, and + hopefully one day Intellij. +
So to use bnd, look at the the descriptions of the tools that + include bnd. Then when you want to find out what a certain command + does, or how to call a macro, then use this manual. +
There are several different ways to get started with bnd. The most elegant + one is to start with Bndtools Eclipse IDE. There + are a number of videos available that provide a gentle introduction. There is also an accompanying + OSGi Starter that provides a lot of detail + how to use bnd for larger projects. It includes a Gradle plugin out of the box that + will build the Bndtools workspace identical to how things are build inside Eclipse + for Continuous Integration with Github Actions or other build servers. +
+ If you insist on using Maven, then you can start with the description of the + Maven plugins. +
+ There is also a Gradle plugin that is the preferred way to build OSGi bundles + with plain old vanilla Gradle. This is described in the Gradle Plugins + readme. +
+ If you just want to figure out how different commands and macros work, the bnd commandline
+ is your friend. Especially the shell
command is very useful exploring it.
+
+ Last but not least, the whole source code is on GitHub. So feel + free to explore it. And when you like, we're very actively taken outside PR's. There is a + lot of activity going on.
+The -augment
instruction can be used to augment resources in the repositories. Augmenting is adding additional capabilities and requirements. When bnd resolves a project or bndrun file then, it will read this variable (it is a merge property so you can also use additional keys like -augment.xyz
) and use it to decorate the repository entries.
The key of the PARAMETER
is for the bundle symbolic name. It can contain the * wildcard character to match multiple bundles. The bundle symbolic name must be allowed as a value in a filter it is therefore not a globbing expression.
The following directives and attribute are architected:
+ +version
– A version range. If a single version is given it will be used as [<version>,∞)
. The version range can be prefixed with an ‘@’ for a consumer range (to the next major) or a provider range (to the next minor) when the ‘@’ is a suffix of the version. The range can restrict the augmentation to a limited set of bundles.capability:
– The capability:
directive specifies a Provide-Capability
instruction, this will therefore likely have to be quoted. Any number of clauses can be specified.requirement:
– The requirement:
directive specifies a Require-Capability
instruction.To augment the repositories during a resolve, bnd will find all bundles that match the bundle symbolic name and fall within the defined range. If no range is given, only the bundle symbolic name will constrain the search. Each found bundle will then be decorated with the capabilities and requirements defined in the capability:
and requirement:
directive.
For example, we need to provide an extender capability to a bundle with the bundle symbolic name com.example.prime
.
-augment.prime = \
+ com.example.prime; \
+ capability:='osgi.extender; \
+ osgi.extender=osgi.component; \
+ version:Version=1.2'
+
The capability:
and requirement:
directives follow all the rules of the Provide-Capability and Require-Capability headers respectively. For the resolver, it is as if these headers were specified in their manifests. There is one exception, the cap:
and req:
directives also support capabilities from the osgi.wiring.*
name spaces.
It should be realized that the augmentation is only used during resolving. These requirements & capabilities are not added to the actual bundles during runtime. The primary purpose is to ensure that the resolve can properly assemble a system; great care should be taken to not create assemblies that cannot be resolved by the framework.
+ +Baselining uses the previous revision of a project with a baseline bundle to detect any changes in semantic versioning using the rules of binary compatibility. The -baseline
instruction enables baselining for one or more symbolic names, the instruction takes a selector as input. Each bundle that is being build is held against this selector and if it matches then it is baselined. The following example will baseline any bundle whose name starts with com.example.
.
-baseline: com.example.*
+
By default a bundle’s baseline is the revision with the highest version in the repositories. However, the baseline can also be set with a file
or version
attribute on the selector.
version
– The target will be the bundle with the lowest version that is higher than the given version.file
– The target will be the given bundle. The file is relative to the project directory.
-baseline com.example.foo;version=1.2, com.example.bar;file=foo-1.2.3.jar
+Detected violations of the semantic versioning are reported as errors.
+ +See baselining for more information.
+ + +The default repository for baselining is the release repository. However, this instruction can be set in the workspace to define an alternate repository. The reference qname
is the name of the repository.
-baselinerepo: Baseline
+
See baselining for more information.
+ +Specified paths must be relative to the project. Each path represents a directory in a project to be ignored by the builder when deciding if the bundles of the project need to be built. This is processed for workspace model builds by the Bndtools builder in Eclipse and the Bnd Gradle plugin.
+ +This can be useful when the workspace is configured to use different output folders for Bndtools in Eclipse and for Gradle. For example:
+ +bin: ${if;${driver;gradle};build/classes/java/main;bin}
+testbin: ${if;${driver;gradle};build/classes/java/test;bin_test}
+target-dir: ${if;${driver;gradle};build/libs;generated}
+
When configuring the workspace to use different output folders for Bndtools in Eclipse and for Gradle, you should also use -builderignore
to instruct the builder to ignore the other builder’s output folders.
-builderignore: ${if;${driver;gradle};bin,bin_test,generated;build}
+
The -buildpath
instruction is the main mechanism to add build-time dependencies to a project. A dependency is either another project in the workspace, or a bundle in a repository. The -buildpath
is only used during compile and build time; it is never used to run projects. Because -buildpath
dependencies are only used compile time it’s recommended to add bundles containing only APIs; you don’t need bundles containing implementations.
An example of the -buildpath
could be the following, where three dependencies are defined:
-buildpath: \
+ some.other.workspace.project;version=project,\
+ osgi.core;version=4.3.1,\
+ osgi.cmpn;version=4.3.1
+
After bnd has build a project it creates 0 or more JARs. If the -buildrepo
is set, the JARs are put
into the given repositories. The syntax is as follows:
-buildrepo ::= repo ( ',' repo )*
+ repo ::= NAME ( ';' NAME ('=' VALUE)? )*
+
The instruction provides a name of a repository, the repository must exist in the workspace. Any properties added to the name are provided as properties in the release context and thus given to the repository.
+ +This feature was inspired by the Maven Bnd Repository. In the Maven development process, projects are installed in the local repository (usually ~/.m2/repository
) so they can be shared with other Maven projects. Setting the -buildrepo
to a Maven repository will allow a bnd workspace to participate in this process on equal footing. Every time the project is build, all its JARs are installed in the associated Maven repository.
For example:
+ +-plugin.maven \
+ aQute.bnd.repository.maven.provider.MavenBndRepository; \
+ name=Local
+
+-buildrepo: Local
+
The install process is taking place in-line with the build process. It is therefore recommended to only use this for local (i.e. file system based) installs.
+ + +In many projects, the workspace carries the files to build it with an external tool. Gradle is the most +popular but it is also possible to use other tools. An advantage of the workspace is that there are +no specific setups required, the workspace contains all the information needed to build. For example, +the bnd command line can build it. Tools are generally used to provide additional tasks.
+ +The setup files of the build tool therefore tend to be dead weight. Dead weight, that frequently needs
+overlapping properties with the bnd workspace. Since the tools tend get updated frequently, there are
+many software engineering advantages in installing the build tool before the project is being built. It
+also makes it easier to maintain many different workspace, they no longer need to be updated when a
+new release becomes available. Only the workspace cnf/build.bnd
needs to be maintained.
TBD: a github action that will run the bnd command is foreseen
+ +The -buildtool
instruction provides the information for the bnd command line tool to install a
+build tool template from the net. It has the following attributes:
version
– If the version
is an OSGi version, the key must be a tool name, for example gradle
. The url used
+to download the template will then be constructed of a url to
+ https://github.com/bndtools/workspace.tool.${key}/archive/refs/tags/${version}.zip
If it is not a version, it can be:
+url
– The key is interpreted as a URL to a template zip file.file
– The key is interpreted to a file local to the root of the workspace*
– Any other attributes will be expanded in a properties file
The template zip must contain a tool.bnd
file. This file describes how the template should be installed. This file
+can be anywhere in the zip hierarchy, its parent directory is the root. Only files from the root will be copied.
The tool.bnd
file must contain a -tool
instruction.
-tool \
+ .*;tool.bnd;skip=true, \
+ gradle.properties;macro=true,\
+ gradlew;exec=true, \
+ *
+
This is a normal instructions. The keys are matched against the file names relative from the root. The first matching +instruction, provides the instructions how to process the file. The following attributes are available.
+ +skip=*
: Skip these filesmacro=*
: Process macros. The macros can use the workspace macros and any macros specified in the tool.bnd
file.append=*
: Append the extra attributes of the -buildtool
instruction to the matching files. This will happen in the format of bnd/properties files.exec=*
: Make the file executable for the ownerThe attributes must have a value, this value is ignored.
+ +We want to use Gradle 7.3 with bnd 6.1.0. We therefore add the following to the bnd workspace cnf/build
file:
-buildtool \
+ gradle; version=7.3.0; \
+ bnd_version = 6.1.0
+ bnd_snapshot=https://bndtools.jfrog.io/bndtools/libs-snapshot-local
+
To install the tool, you can execute the bnd command buildtool
:
$ bnd buildtool --force
+
This will download the sources at https://github.com/bndtools/workspace.tool.gradle/archive/refs/heads/7.3.0.zip
. The zip file
+looks something like:
workspace.tool.gradle/
+ tool.bnd
+ gradle/
+ wrapper/
+ gradle-wrapper.jar
+ gradle-wrapper.properties
+ gradle.properties
+ settings.gradle
+ gradlew.bat
+ gradlew
+ .gitignore
+
The tool.bnd
looks like:
-tool \
+ .*;tool.bnd;skip=true, \
+ gradle.properties;macro=true,\
+ gradlew;exec=true, \
+ *
+
It should be clear that the gradle.properties
is expanded. After the files are copied, it looks like:
# Appended by tool manager at 2021-12-02T14:40:55.660843Z
+version = url
+bnd_version = 6.1.0
+bnd_snapshots = https://bndtools.jfrog.io/bndtools/libs-snapshot-local
+
After the command has finished, the gradle command can be executed
+ +$ ./gradlew --parallel clean build
+
The bump
command increases the versions in a bnd project. By default, it will use a compatible policy. A compatible to increment the minor part of the version and reset the micro part. The major part will remain the same. Using the versionmask
syntax this looks like:
=+0
+
The -bumppolicy
macro can override this default. For example, the following instruction sets the policy to make only major increments.
+00
+
The -bundleannotations
instruction tells bnd which bundle classes, if any, to search for OSGi Bundle annotations. bnd will then process those annotations into manifest headers.
The value of this instruction is a comma delimited list of fully qualified class names.
+ +The default value of this instruction is *
, which means that by default bnd will process all bundle classes looking for Bundle annotations.
The -cdiannotations
instruction tells bnd which bundle classes, if any, to search for OSGI CDI Integration (or plain CDI) annotations. bnd will then process those classes into requirements and capabilities.
The value of this instruction is a comma delimited list of globs matching packages or classes by fully qualified name.
+ +The default value of this instruction is *
, which means that by default bnd will process all bundle classes looking for OSGI CDI Integration (or plain CDI) annotations.
Each glob may specify the discover
attribute which determines the bean discovery mode to apply to matches.
The following discover
modes are supported:
discover |
+ Bean Discovery Mode | +
---|---|
all |
+ include all classes in the bundle as CDI beans | +
annotated |
+ include classes annotated with bean defining annotations as CDI beans | +
annotated_by_bean (default) |
+ include classes annotated with org.osgi.service.cdi.annotations.Bean or classes in packages annotated with org.osgi.service.cdi.annotations.Beans as CDI beans. This is the default mode when discover is not specified. |
+
none |
+ do not include any classes in the bundle as CDI beans | +
-cdiannotations: *;discover=all
+
Each glob may specify the noservicecapabilities
attribute which indicates that no service capabilities will be added for matches.
-cdiannotations: *;discover=annotated;noservicecapabilities=true
+
Each glob may specify the noservicerequirements
attribute which indicates that no service requirements will be added for matches.
-cdiannotations: *;noservicerequirements=true
+
Adding extra checks to bnd will break existing builds and some people get a tad upset about that. However, some checks are actually really valuable. So this instruction is a contract. New checks that can break build will add additional enums to this list. So in theory builds should not be broken. Currently we have the following values defined
+ +ALL
– Enable all checking, including checks that are added in the future. So for ALL
, one day we might break your build. Your choice. Then again, this is the wisest choice since the checks we do are really useful to know if they are not satisfied.EXPORTS
– Enable checking for exports. This checks if an exported package has contents.IMPORTS
– Enable checking of imports. If an import is not exported by any bundle on the -buildpath
then we have a bit of a problem. Trying to resolve the current bundle on a framework will almost certainly fail. If you really need an import but have no export then you can get rid of the warning by explicitly importing it in Import-Package. For example:
Import-Package: i.do.no.exist;version=@1.0.0, *
+The -classpath
instruction adds class path entries to a bnd file’s processing. It contains direct references to JARs through file paths or URLs. Entries on the class path are made available as imports and can be used as private or exported packages.
-classpath: jar/foo.jar, jar/bar.jar
+
If you need to get all JARs in a directory you could use the ${lsa}
macro:
-classpath: ${lsa;jar/*}
+
This instruction should only be used when bnd is used in stand alone mode. In project mode (when you have a workspace), the -buildpath
defines the actual class path. However, even in project mode any -classpath
entries are added to the actual class path.
When a Jar is build it has default DEFLATE compression with the default level. Using this instruction you +can override the compression method.
+ +-compression: STORE
+
This instruction is equal to using -conditionalpackage except for the fact that the header in addition will be copied into the generated bundle manifest (like all headers beginning with a capital letter).
+ +The -conditionalpackage
instruction implements a feature that is either looked up in awe (when understood) or regarded with disgust. Though one has to be very careful with its use, it is a feature that can significantly reduce runtime dependencies. So what does it do? Well, it copies the content of packages from the current class path into your bundle that:
The purpose of this instruction is very much the same as static linking. Unix’es have a similar feature with static linking. It addresses the problem of util libraries. Utility libraries are extremely useful because they allow us to create primitives we can use in many places. However, in reality utility libraries are often quite, well let’s say, all over the place. As a result utility libraries tend to drag in a lot of transient dependencies that are actually not all needed. As a common example, someone once used a single method from an Apache Commons library and dragged in 20 Mb of dependencies.
+ +The purpose therefore of the -conditionalpackage
instruction is to pick cohesive packages from a utility library and copy them in the bundle. Any dependencies of those copied packages will also be copied if they match the selectors.
The packages that are copied cannot be exported, they must be private. This makes it possible to rely on the exact version that the bundle is compiled against. It also ensures that no information is leaked between bundles when statics are used.
+ +The given PACKAGE-SPEC
follows the format outlined in Selector.
For example:
+ +-conditionalpackage: aQute.lib*
+
will slurp any packages that have a name that matches aQute.lib*
(e.g. both aQute.lib
and aQute.libg
) and are referred to by the current JAR’s contents.
On the other hand the example:
+ +-conditionalpackage: mypackage.example.*
+
will copy the package mypackage.example
and all its sub-packages into the bundle in case they are referred to by the current JAR’s contents.
A good example of a suitable library is the aQute.libg project. It is a collection of packages that each implement a single function. This ranges from a Strings class with simple String utilities, all the way to a Trajan graph analyzer and simple fork-join framework. Though some of the packages are dependent on each other, most packages have no dependencies whatsoever.
+ +When preparing a library for -conditionalpackage
you should take the following into account:
One objection that is raised against this model is that you copy code. However, this model only copies the binaries, not source code, therefore, there is no real duplication. Yes, they then say, but when there is a bug I need to fix in many places? Well, the bundles will have to be rebuild but that is generally a good idea when a dependency changes. And think about it, that bundle was tested with the buggy code, it is highly unlikely that this bug seriously affects the bundle.
+ + +The -conduit
instruction allows a Project to act as a conduit to one or more actual JARs on the file system. That is, when the Project is build it will not build those JARs, it just returns them as the result of the project. This can be useful when a Project is moved elsewhere but must still be part of the build because, for example, it needs to be part of the release process.
-conduit: jar/foo.jar
+
Notice that you can use the ${lsa}
macro to get the contents of a directory:
-conduit: ${lsa;jar/*.jar}
+
This chapter discusses the connection settings of bnd that are used when bnd is asked to download or upload a file +from a remote server. The connection settings can provide a userid/password for basic authentication, proxies, and/or +the trust policy for verifying the host name of a TLS/SSL server. These settings can obviously not be part of the workspace +since they are unique to the actual user of the workspace. That is, they need to be stored outside the workspace +in a user accessible area.
+ +Since Maven is very popular we’ve followed the same syntax for settings. The supported elements in this file are
+<server>
and <proxy>
. Other elements are ignored.
The default order in which bnd looks for settings is:
+ +`~/.bnd/connection-settings.xml`
+`~/.m2/settings.xml`
+
If you want to disable the use of default from the workspace then you can use the -connection-settings
instruction
+in the workspace’s cnf/build.bnd
file. If you set the instruction to false
then it will not look for the aforementioned files.
-connection-settings: false
+
In this setting, you can also list additional files to parse that must be of the same syntax as the Maven settings file. The names
+maven
and bnd
are recognized as ~/.m2/settings.xml
and ~/.bnd/connection-settings.xml
respectively. For example, if
+we first want to look in the home directory in ~/foo/settings.xml
and then in the bnd settings, we can set the -connection-settings
to:
-connection-settings: ~/foo/settings.xml, bnd
+
In addition, you could also specify the maven settings inline. For example:
+ +-connection-settings: \
+ server; \
+ id="http://server"; \
+ username="myUser"; \
+ password=${env;PASSWORD}
+
You can create a log file specific for the connections by specifying:
+ +-connection-log: somefile.txt
+
This file will contain the detailed trace output. The file given is relative to the working directory.
+ +The settings files have the following XML structure:
+ +<settings>
+ <proxies>
+ <proxy>
+ <id/>
+ <active> 'true' | 'false' </active>
+ <protocol> 'http'| 'direct' | 'socks' </protocol>
+ <host> domain name </host>
+ <port> port to use </port>
+ <username> user name to use for the proxy </username>
+ <password> password to use for the proxy </password>
+ <verify> true | false </verify>
+ <nonProxyHosts> glob ( '|' glob )* </nonProxyHosts>
+ </proxy>
+ </proxies>
+ <servers>
+ <server>
+ <id>default</id>
+ <username>username</username>
+ <password>password</password>
+ <verify> true | false </verify>
+ <trust> comma separated paths to X509 certificates </trust>
+ <maxConcurrentConnections>10</maxConcurrentConnections>
+ </server>
+ </servers>
+</settings>
+
Any additional elements are ignored.
+ +The purpose of the proxy definitions is to define a communication proxy. We support HTTP
and SOCKS
proxies. An additional built-in proxy is the DIRECT
‘proxy’. Proxies can be authenticated with a username and password.
Tag | +Default | +Values | +Description | +
---|---|---|---|
id |
+ default |
+ NAME |
+ Identifies the proxy, must be unique in | +
+ | + | + | the list of proxies. | +
active |
+ true |
+ true | false |
+ If this proxy is active | +
protocol |
+ http |
+ socks | http | direct |
+ The proxy protocol to use. | +
host |
+ + | domain-name |
+ The domain name or IP address of the proxy host | +
port |
+ + | INTEGER |
+ Port number, maybe absent if default | +
+ | + | + | port for protocol | +
username |
+ + | + | User name for authentication to the proxy | +
password |
+ + | + | Password for authentication to the proxy | +
nonProxyHosts |
+ none | +GLOB ('|' GLOB)* |
+ Filter on the destination hosts that should not be proxied | +
In maven, the servers are identified by an id; when you define a repository you tell which id to use. In bnd this works
+slightly different. Instead of using the id we use the id in the settings as a glob expression on the protocol, host name,
+and port number. The glob expression must match the scheme, the host and the port if the port is not the default port.
+To match the url https://maven2-repository.dev.java.net/
, the server component could look like (pay attention to the missing trailing slash):
<server>
+ <id>https://*java.net</id>
+ ...
+</server>
+
The first server that matches the id will provide the parameters.
+ +Tag | +Default | +Values | +Description | +
---|---|---|---|
id |
+ + | GLOB |
+ A glob expression on the scheme + host + port | +
username |
+ + | + | User name for authentication to the server | +
password |
+ + | + | Password for authentication to the server | +
verify |
+ true |
+ false | true |
+ Enable/disable the verification of the host name against a certificate for HTTPS | +
trust |
+ + | + | Provide paths to certificate that provide trust to the host certificate. The format most of a X.509 certificate file. Normally the extension is .cer |
+
maxConcurrentConnections |
+ 0 (unlimited) | +0.. | +Limits the number of parallel connections to a host. Some hosts use the number of concurrent connections as a sign of a denial of service attack. | +
It also supports Bearer (OAuth2) authentication. If the <server>
configuration has only a password and no username, then Bearer authentication is in effect with the password used as the token.
-connection-settings: server;id="https://*.server.com";password="oauth2token"
+
will cause
+ +Authorization: Bearer oauth2token
+
request header to be sent to servers matching the glob https://*.server.com.
+ +See https://github.github.com/maven-plugins/site-plugin/authentication.html for an example of a <server>
configuration for OAuth2.
The bnd command line provides a number of commands to display and verify the settings as well as getting the information from +getting a remote file.
+ +$ bnd com
+Available sub-commands:
+
+clear - Clear the cached file that is
+ associated with the givenURI
+info - Show the information used by the
+ Http Client to get aremote file
+settings - Show the bnd -connection-settings
+
Certificate authentication is only suppprted using the Java settings through system properties. Either with -D
on the command line
+or in the eclipse.ini
file or through some other means. In that case, make sure not specify the <trust/>
nor <verify/>
element
+in a connection-settings.xml
. The reason is that once you specify those element, the built-in Java mechanism is replaced with bnd code.
The -consumer-policy
instruction defines the semantic versioning policy to be used when a type is a consumer. A consumer is in general a type that is implemented by classes that are just users of the package. In contrast, a provider is the party that is basically responsible for the contract defined in the package. For example, when you implement Event Admin, the org.osgi.service.event package is your responsibility so the types you need to implement like EventAdmin
are provider types. (These types are annotated with a @ProviderType
annotation.) A casual user of the Event Admin service will be a consumer, the EventHandler
type is therefore annotated with a@ConsumerType
.
The purpose of this distinction is semantic versioning. It turns out that the relation between a consumer and a provider is not symmetric. A provider is tightly bound to a contract while a consumer is expected to have backward compatibility. Virtually any change to the contract requires the provider to adapt while a consumer is in almost all cases protect against changes.
+ +This asymmetry has a consequence for the semantic versioning. In the OSGi, the semantics are defined that a micro change does not affect the provider nor the consumer. A minor change affects the provider, and a major change affects both. Therefore, a bundle that implements a provider type must import a range from major.minor.micro
… major.minor+1.0
. A bundle that implements a consumer type must import major.minor.micro
… major+1.0.0
.
In theory, bnd could have hard coded these policies but there are always cases where the policy is just not right. The -consumer-policy
specifies the macro to use for calculating the version range. The default definition is:
-consumer-policy ${range;[==,+)}
+-provider-policy ${range;[==,=+)}
+
The range macro works very much like the version macro. It uses a template to define a change the range/version.
+ +The provider and consumer policy are global and this is not very convenient if you want to make an exception just for a specific bundle. For example, a bundle coming from Gavin King’s Ceylon. For this reason, you can also specify a policy on an import:
+ +Import-Package com.gavinking.*;version="${range;[--,++)}", *
+
The counterpart of the -consumer-policy
is of course the -provider-policy.
Though the OSGi has a very elegant package version model there are still many that think this is too much work. They do not want to be bothered by the niceties of semantic versions and just want to use, let’s say, Servlet 3.0. For those people (seemingly not interested in minimizing dependencies) the OSGi Alliance came up with contracts in OSGi Core, Release 5.0.0. A contract allows you to:
+ +This very common pattern is called the Capability/Requirement (C/R) model in OSGi, it underlies all of its dependency concepts like Import/Export package and others; it forms the foundation of the OSGi Bundle Repository. If you ever want to know what is happening deep down inside a framework than look at the Wiring API and you see the requirements and capabilities in their most fundamental form.
+ +Capabilities declare a set of properties that describe something that a bundle can provide. A Requirement in a bundle has a filter that must match a capability before this bundle can be resolved. To prevent requirements matching completely unrelated capabilities they must both be defined in the same namespace, where the namespace then defines the semantics of the properties. Using the C/R model we were able to describe most of the OSGi dependencies with surprisingly few additional concepts. For a modern OSGi resolver there is very little difference between the Import-Package
and Require-Bundle
headers.
So how do those contracts work? Well, the bundle that provides the API for the contract has a contract capability. What this means is that it provides a Provide-Capability
clause in the osgi.contract
namespace, for example:
# Bundle P:
+Provide-Capability:\
+ osgi.contract;\
+ osgi.contract=Servlet;\
+ uses:="javax.servlet,javax.servlet.http";\
+ version="3.0"
+Export-Package: javax.servlet, javax.servlet.http
+
This contract defines two properties, the contract name (by convention this is the namespace name as property key) and the version. A bundle that wants to rely on this API can add the following requirement to its manifest:
+ +# Bundle R:
+Require-Capability:\
+ osgi.contract;\
+ filter:="(&(osgi.contract=Servlet)(version=3.0))"
+Import-Package: javax.servlet, javax.servlet.http
+
Experienced OSGi users should have cringed at these versionless packages, cringing becomes a gut-reaction at the sight of versionless packages. However, in this case it actually cannot harm. The previous example will ensure that Bundle P will be the class loader for the Bundle R for packages javax.servlet, javax.servlet.http. The magic is in the uses:
directive, if the Require-Capability
in bundle R is resolved to the Provide-Capability
in bundle P then bundle R must import these packages from bundle P.
Obviously bnd has support for this (well, since today, i.e. version osgi:biz.aQute.bndlib@2.2.0.20130806-071947
or later). First bnd can make it easier to create the Provide-Capability
header since the involved packages are in the Export-Package
as well as in the Provide-Capability
headers. The do-no-repeat-yourself mantra dictated am ${exports}
macro. The ${exports}
macro is replaced by the exported packages of the bundle, for example:
# Bundle P:
+Provide-Capability:\
+ osgi.contract;\
+ osgi.contract=Servlet;\
+ uses:="${exports}";\
+ version="3.0"
+Export-Package: javax.servlet, javax.servlet.http
+
That said, the most extensive help you get from bnd is for requiring contracts. Providing a contract is not so cumbersome, after all you’re the provider so you have all the knowledge and the interest in providing metadata. Consuming a contract is less interesting and it is much harder to get the metadata right. In a similar vein, bnd analyzes your classes to find out the dependencies to create the Import-Package
statement, doing this by hand is really hard (as other OSGi developing environments can testify!)
So to activate the use of contracts, add the -contract
instruction:
# bnd.bnd:
+-contract: *
+
This instruction will give bnd permission to scan the build path for contracts, i.e. Provide-Capability
clauses in the osgi.contract
namespace. These declared contracts cause a corresponding requirement in the bundle when the bundle imports packages listed in the uses clause. In the example with Bundle R, bnd will automatically insert the Require-Capability
header and remove any versions on the imported packages.
Sometimes the wildcard for the -contract
instruction can be used to limit the contracts that are considered. Sometimes you want a specific contract but not others. Other times you want to skip a specific contract. The following example skips the ‘Servlet’ contract:
bnd.bnd:\
+ -contract: !Servlet,*
+
Note: As of bnd 4.1.0 the default value for the -contract
instruction will be *
which result in the automatic application of any contracts found at build time.
The tests provide some examples for people that want to have a deeper understanding: https://github.com/bndtools/bnd/blob/next/biz.aQute.bndlib.tests/src/test/ContractTest.java
+ +See also Portable Contract Definitions
+ + +Used in conjunction with the -contract
instruction,-define-contract
can be applied in order to define a contract which is not available on the build path (i.e. compile class path).
The contract specification is exactly the same syntax used in the Provide-Capability
header.
-define-contract:\
+ osgi.contract;\
+ osgi.contract=JavaServlet;\
+ uses:="javax.servlet,javax.servlet.annotation,javax.servlet.descriptor,javax.servlet.http";\
+ version="3.0"
+
Now if the current bundle imports packages declared in the uses
directive of the defined contract above, they will be imported without a package version, and a contract requirement will be added exactly as if there had been a contract on the build path.
Note the -contract
instruction defaults to *
(all contracts) and so it can be omitted when using the -define-contract
instruction.
See also Portable Contract Definitions
+ + +Projects referenced by -buildpath are always built first but sometimes
+you need to specify projects to be build first which are not referenced by -buildpath
.
+You can specify those additional projects using -dependson
.
-dependson: projA, projB
+
NEEDS WORK?
+ +/**
+ * Deploy the current project to a repository
+ *
+ * @throws Exception
+ */
+public void deploy() throws Exception {
+ Parameters deploy = new Parameters(getProperty(DEPLOY));
+ if (deploy.isEmpty()) {
+ warning("Deploying but %s is not set to any repo", DEPLOY);
+ return;
+ }
+ File[] outputs = getBuildFiles();
+ for (File output : outputs) {
+ for (Deploy d : getPlugins(Deploy.class)) {
+ trace("Deploying %s to: %s", output.getName(), d);
+ try {
+ if (d.deploy(this, output.getName(), new BufferedInputStream(new FileInputStream(output))))
+ trace("deployed %s successfully to %s", output, d);
+ }
+ catch (Exception e) {
+ msgs.Deploying(e);
+ }
+ }
+ }
+}
+
/**
+ * Deploy the file (which must be a bundle) into the repository.
+ *
+ * @param file
+ * bundle
+ */
+public void deploy(File file) throws Exception {
+ String name = getProperty(Constants.DEPLOYREPO);
+ deploy(name, file);
+}
+
+/**
+ * Deploy the file (which must be a bundle) into the repository.
+ *
+ * @param name
+ * The repository name
+ * @param file
+ * bundle
+ */
+public void deploy(String name, File file) throws Exception {
+ List<RepositoryPlugin> plugins = getPlugins(RepositoryPlugin.class);
+
+ RepositoryPlugin rp = null;
+ for (RepositoryPlugin plugin : plugins) {
+ if (!plugin.canWrite()) {
+ continue;
+ }
+ if (name == null) {
+ rp = plugin;
+ break;
+ } else if (name.equals(plugin.getName())) {
+ rp = plugin;
+ break;
+ }
+ }
+
+ if (rp != null) {
+ try {
+ rp.put(new BufferedInputStream(new FileInputStream(file)), new RepositoryPlugin.PutOptions());
+ return;
+ }
+ catch (Exception e) {
+ msgs.DeployingFile_On_Exception_(file, rp.getName(), e);
+ }
+ return;
+ }
+ trace("No repo found " + file);
+ throw new IllegalArgumentException("No repository found for " + file);
+}
+
You can use the -diffignore
instruction to specify manifest header names
+and resource paths to ignore during baseline comparison.
-diffignore: com/foo/xyz.properties, Some-Manifest-Header
+
You can use the -diffpackages
instruction to specify the names of exported packages
+to be baseline compared. The default is all exported packages.
-diffpackages: !*.internal.*, *
+
/**
+ * Check if we need to calculate any checksums.
+ *
+ * @param dot
+ * @throws Exception
+ */
+private void doDigests(Jar dot) throws Exception {
+ Parameters ps = OSGiHeader.parseHeader(getProperty(DIGESTS));
+ if (ps.isEmpty())
+ return;
+ trace("digests %s", ps);
+ String[] digests = ps.keySet().toArray(new String[ps.size()]);
+ dot.setDigestAlgorithms(digests);
+}
+
+
+
+
+/**
+ * Create a standalone executable. All entries on the runpath are rolled out
+ * into the JAR and the runbundles are copied to a directory in the jar. The
+ * launcher will see that it starts in embedded mode and will automatically
+ * detect that it should load the bundles from inside. This is drive by the
+ * launcher.embedded flag.
+ *
+ * @throws Exception
+ */
+
+@Override
+public Jar executable() throws Exception {
+
+ // TODO use constants in the future
+ Parameters packageHeader = OSGiHeader.parseHeader(project.getProperty("-package"));
+ boolean useShas = packageHeader.containsKey("jpm");
+ project.trace("Useshas %s %s", useShas, packageHeader);
+
+ Jar jar = new Jar(project.getName());
+
+ Builder b = new Builder();
+ project.addClose(b);
+
+ if (!project.getIncludeResource().isEmpty()) {
+ b.setIncludeResource(project.getIncludeResource().toString());
+ b.setProperty(Constants.RESOURCEONLY, "true");
+ b.build();
+ if ( b.isOk()) {
+ jar.addAll(b.getJar());
+ }
+ project.getInfo(b);
+ }
+
+ List<String> runpath = getRunpath();
+
+ Set<String> runpathShas = new LinkedHashSet<String>();
+ Set<String> runbundleShas = new LinkedHashSet<String>();
+ List<String> classpath = new ArrayList<String>();
+
+ for (String path : runpath) {
+ project.trace("embedding runpath %s", path);
+ File file = new File(path);
+ if (file.isFile()) {
+ if (useShas) {
+ String sha = SHA1.digest(file).asHex();
+ runpathShas.add(sha + ";name=\"" + file.getName() + "\"");
+ } else {
+ String newPath = "jar/" + file.getName();
+ jar.putResource(newPath, new FileResource(file));
+ classpath.add(newPath);
+ }
+ }
+ }
+
+ // Copy the bundles to the JAR
+
+ List<String> runbundles = (List<String>) getRunBundles();
+ List<String> actualPaths = new ArrayList<String>();
+
+ for (String path : runbundles) {
+ project.trace("embedding run bundles %s", path);
+ File file = new File(path);
+ if (!file.isFile())
+ project.error("Invalid entry in -runbundles %s", file);
+ else {
+ if (useShas) {
+ String sha = SHA1.digest(file).asHex();
+ runbundleShas.add(sha + ";name=\"" + file.getName() + "\"");
+ actualPaths.add("${JPMREPO}/" + sha);
+ } else {
+ String newPath = "jar/" + file.getName();
+ jar.putResource(newPath, new FileResource(file));
+ actualPaths.add(newPath);
+ }
+ }
+ }
+
+ LauncherConstants lc = getConstants(actualPaths, true);
+ lc.embedded = !useShas;
+ lc.storageDir = null; // cannot use local info
+
+ final Properties p = lc.getProperties();
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ p.store(bout, "");
+ jar.putResource(LauncherConstants.DEFAULT_LAUNCHER_PROPERTIES, new EmbeddedResource(bout.toByteArray(), 0L));
+
+ Manifest m = new Manifest();
+ Attributes main = m.getMainAttributes();
+
+ for (Entry<Object,Object> e : project.getFlattenedProperties().entrySet()) {
+ String key = (String) e.getKey();
+ if (key.length() > 0 && Character.isUpperCase(key.charAt(0)))
+ main.putValue(key, (String) e.getValue());
+ }
+
+ Instructions instructions = new Instructions(project.getProperty(Constants.REMOVEHEADERS));
+ Collection<Object> result = instructions.select(main.keySet(), false);
+ main.keySet().removeAll(result);
+
+ if (useShas) {
+ project.trace("Use JPM launcher");
+ m.getMainAttributes().putValue("Main-Class", JPM_LAUNCHER_FQN);
+ m.getMainAttributes().putValue("JPM-Classpath", Processor.join(runpathShas));
+ m.getMainAttributes().putValue("JPM-Runbundles", Processor.join(runbundleShas));
+ URLResource jpmLauncher = new URLResource(this.getClass().getResource("/" + JPM_LAUNCHER));
+ jar.putResource(JPM_LAUNCHER, jpmLauncher);
+ } else {
+ project.trace("Use Embedded launcher");
+ m.getMainAttributes().putValue("Main-Class", EMBEDDED_LAUNCHER_FQN);
+ m.getMainAttributes().putValue(EmbeddedLauncher.EMBEDDED_RUNPATH, Processor.join(classpath));
+ URLResource embeddedLauncher = new URLResource(this.getClass().getResource("/" + EMBEDDED_LAUNCHER));
+ jar.putResource(EMBEDDED_LAUNCHER, embeddedLauncher);
+ }
+ if ( project.getProperty(Constants.DIGESTS) != null)
+ jar.setDigestAlgorithms(project.getProperty(Constants.DIGESTS).trim().split("\\s*,\\s*"));
+ else
+ jar.setDigestAlgorithms(new String[]{"SHA-1", "MD-5"});
+ jar.setManifest(m);
+ return jar;
+}
+
The -distro
instruction is used in the case that your application must run in a host environment, for example Karaf. In such cases it is not possible to calculate the system capabilities from the framework and the run path. Each of these host environments has a specific set of capabilities that should be used as input to the resolver.
The -distro
capability has the same syntax as the -runpath
, a list of bundle specifications. The resolver will parse these bundles and treat their capabilities specified with the Provide-Capability
header in their Manifest as the system capabilities. These files can be generated manually. However, the bnd command line tool can create a distro bundle using the remote agent.
When the -distro
is present in the bndrun
file it overrides any other definition that are used to derive capabilities. If additional capabilities are needed the -runprovidedcapabilities
should be used.
For example:
+ +-distro: karaf-4.1.jar;version=file
+
When -includeresource
copies files from another JAR or the file system it will look at the -donotcopy
+instruction. This instruction is a single regular expression. Any resource that is copied is matched
+against this list. If there is a match, then the file is ignored.
For example (and also the defaults)
+ +-donotcopy: CVS|\\.svn|\\.git|\\.DS_Store|\\.gitignore
+
The -dsannotations-options
instruction configures how DS component annotations are processed and what metadata is generated.
-dsannotations-options: version;minimum=1.2.0;maximum=1.3.0
+
The example above, will restrict the use of OSGi DS annotations to minimum 1.2.0 and maximum 1.3.0 version. The version number denotes that the users are free to use any version equal to or higher than 1.2.0 but less than or equal to 1.3.0, provided that the users have the DS annotations included on the build path.
+ +The following options are supported:
+ +option | ++ |
---|---|
inherit | +use DS annotations found in the class hierarchy of the component class. WARNING: Annotations are not inherited from the component’s super classes by default. The problem is that super classes from imported packages may be different at runtime than they were at build time. So it is always best to declare your annotations on the actual component class. | +
felixExtensions | +enable features proprietary to Apache Felix SCR | +
extender | +add the osgi.extender=osgi.component requirement to the manifest |
+
nocapabilities | +do not add osgi.service capabilities to the manifest |
+
norequirements | +do not add osgi.service requirements to the manifest |
+
version | +set the minimum and/or maximum version of the osgi.extender=osgi.component requirement added to the manifest |
+
The -dsannotations
instruction tells bnd which bundle classes, if any, to search for Declarative Services (DS) annotations. bnd will then process those classes into DS XML descriptors.
The value of this instruction is a comma delimited list of fully qualified class names.
+ +The default value of this instruction is *
, which means that by default bnd will process all bundle classes looking for DS annotations.
The behavior of DS annotation processing can be further configured using the -dsannotations-options instruction.
+ + + + /**
+ * Added for 1.8 profiles. A 1.8 profile is a set of packages so the VM can
+ * be delivered in smaller versions. This method will look at the
+ * {@link Constants#EEPROFILE} option. If it is set, it can be "auto" or it
+ * can contain a list of profiles specified as name="a,b,c" values. If we
+ * find a package outside the profiles, no profile is set. Otherwise the
+ * highest found profile is added. This only works for java packages.
+ */
+private String doEEProfiles(JAVA highest) throws IOException {
+ String ee = getProperty(EEPROFILE);
+ if (ee == null)
+ return highest.getFilter();
+
+ ee = ee.trim();
+
+ Map<String,Set<String>> profiles;
+
+ if (ee.equals(EEPROFILE_AUTO_ATTRIBUTE)) {
+ profiles = highest.getProfiles();
+ if (profiles == null)
+ return highest.getFilter();
+ } else {
+ Attrs t = OSGiHeader.parseProperties(ee);
+ profiles = new HashMap<String,Set<String>>();
+
+ for (Map.Entry<String,String> e : t.entrySet()) {
+ String profile = e.getKey();
+ String l = e.getValue();
+ SortedList<String> sl = new SortedList<String>(l.split("\\s*,\\s*"));
+ profiles.put(profile, sl);
+ }
+ }
+ SortedSet<String> found = new TreeSet<String>();
+ nextPackage: for (PackageRef p : referred.keySet()) {
+ if (p.isJava()) {
+ String fqn = p.getFQN();
+ for (Entry<String,Set<String>> entry : profiles.entrySet()) {
+ if (entry.getValue().contains(fqn)) {
+
+ found.add(entry.getKey());
+
+ //
+ // Check if we found all the possible profiles
+ // that means we're finished
+ //
+
+ if (found.size() == profiles.size())
+ break nextPackage;
+
+ //
+ // Profiles should be exclusive
+ // so we can break if we found one
+ //
+ continue nextPackage;
+ }
+ }
+
+ //
+ // Ouch, outside any profile
+ //
+ return highest.getFilter();
+ }
+ }
+
+ String filter = highest.getFilter();
+ if (!found.isEmpty())
+ filter = filter.replaceAll("JavaSE", "JavaSE/" + found.last());
+ // TODO a more elegant way to build the filter, we now assume JavaSE
+ return filter;
+
+}
+
Bundles are stored in a jar
directory in the executable JAR. The name in this directory is by default the file name in the repository. This filename is, however, also used by the launcher as the location. This implies that if the file name contains the version, a new location is created when the version changes. This ends up with too many duplicates quickly. This is only a problem when you keep the storage area.
The location
option in the -executable
instructions can provide a printf format to calculate a location based on the Bundle-SymbolicName and Bundle-Version.
For example:
+ +-executable: location='${@bsn}-${version;=;${@version}}'
+
This format will create locations where a bundle will overwrite when the major part of the version is the same. I.e. com.example.foo-1
. I.e., it will allow multiple bundles that are semantically incompatible but compatible bundles overwrite each other.
When an executable JAR is created by the Project Launcher the compression is controlled with the -compression +instruction. However, this über JAR contains bundles and JARs for the run path. Since executable JARs are sometimes used in +embedded environments it is important that they are small and the code can loaded quickly. It is difficult to +have defaults for this since there are many aspects:
+ +In such a complex trade off there are no singular answers. This -executable
instruction allows therefore to
+set the different options and then benchmark the result.
Experience shows that the size can be minimized by rejarring the inner bundles and JARs with the STORE compression +but using DEFLATE for the outer/über JAR. This decreases the size because the compression algorithm then works on +all class files of all bundles. This is much more efficient for the DEFLATE algorithm then compressing each class file +on its own. Gains seen are 20%-30%.
+ +The rejar
option in the -executable
instruction can take the values STORE
or DEFLATE
. This will open
+the embedded JARs and write them in the given compression in the über JAR.
For example:
+ +-executable: rejar=STORE
+
The default is to not touch the bundle.
+ +The strip
option can be used to strip resources from JARs embedded in the execetable. Its parameter can define
+a list of two GLOBs. The first GLOB must match the file name of the resource and the second GLOB must match the
+resource name in the bundle. GLOBs are separated with a colon (‘:’). If no colon is present then the first GLOB
+is assumed to be the wildcard GLOB that matches anything.
Syntax:
+ +strip ::= matcher ( ',' matcher )*
+matcher ::= ( GLOB ':' ) GLOB
+
Some examples:
+ +*.map
– Match all resources ending in .map
OSGI-OPT/*
– Remove the optional resources in bundlecom.example.some.bundle.*:readme.md
– Remove the readme’s from bundles withe file name matching com.example.some.bundle.*
.Note hat if you combine multiple GLOBs then you must separate them with a comma (‘,’) and that implies the total must be +quoted with single or double quotes. If it is a single GLOB (pair) then quoting is optional. For example:
+ +-executable: rejar=STORE, strip='OSGI-OPT,*.map'
+
The default is to not strip anything.
+ +Rejarring and stripping should work for unsigned bundles since the signatures should not be affected by the +compression algorithms used.
+ +API Guardian is a
+ +++ +Library that provides the
+@API
annotation that is used to annotate public types, methods, constructors, and fields within a framework or application in order to publish their status and level of stability and to indicate how they are intended to be used by consumers of the API.
The heuristic used by bnd to determine which packages to export is as follows:
+ +-export-apiguardian
is required (i.e. opt-in is required). The value of the instruction is a package specification like most other bnd instructions. The minimal configuration is -export-apiguardian: *
which instructs bnd to scan all classes in all packages with the plugin@API
annotationstatus
using the highest value of org.apiguardian.api.API.Status
(in ordinal order) found on @API
annotations in the packagemandatory:=status
for any package whose highest value of org.apiguardian.api.API.Status
found was INTERNAL
. This implies that the package can only be imported by marking the import with the attribute status=INTERNAL
Export all packages annotated with @API
using version 1.2.3
:
-export-apiguardian: *;version="1.2.3"
+
Export only packages annotated with @API
named com.acme.foo.*
but not com.acme.foo.bar.*
-export-apiguardian:\
+ !com.acme.foo.bar.*,\
+ com.acme.foo.*
+
The -export
instruction can be used to export a bndrun
or bnd
file to an executable JAR during the build process. In contrast to the export functions, this -export
instruction is supported in all drivers, including Eclipse. It can be used to continously build executable JARs or other supported exporters. It can only be used in the top level bnd.bnd file.
The format of the -export
instruction is:
-export ::= filespec ( ',' filespec )*
+filespec ::= path (';' PARAMATER )*
+
The path
is either a relative path in the project directory or it is a wildcard specification using globs. All files in the project directory are selected. In general, these should be bnd or bndrun files.
The following attributes are architected:
+ +type
– The type specifies the exporter type. The default type is bnd.executablejar.pack
.name
– Overrides the default name of the output file. It should contain the extension. The file should not have path segments, it will be placed in the target directory. Without a name, the exporter defines the file name since the extensions can differ depending on the exporter.bsn
– Set the bundle symbolic name of the resulting JAR file if possibleversion
– Set the version of the resulting JAR file if possible-profile
can be used to select a specific property profile.The -exporttype
can be used to set some default attributes for a specific type.
The Exporters use a plugin mechanism and therefore the list is not closed. The following exporters are supported by bnd out of the box:
+ +bnd.executablejar.pack
– Exports a JAR file using the launcher from the -runpath
or the default if no launcher is specified.bnd.executablejar
– Similar to the previous but does not support profiles, has no automatic bsn assigned, and entries are not signed. The reason there are two types for the more or less the same format with subtle differences is for backward compatibility.bnd.runbundles
– A JAR with the runbundles. As optional Attributes targetDir
will output all runbundles in the set directory in the resulting jar and template
will use a given jar/zip or directory as template to which the bundles will be added.osgi.subsystem.application
– Export into an application subsystemosgi.subsystem.feature
– Export into a feature subsystemosgi.subsystem.composite
– Export into a composite subsystemp2
– Export a P2 repositoryFor example:
+ +-export: \
+ foo.bndrun; \
+ -profile=debug; \
+ -runkeep=true; \
+ name = "foo.jar, \
+ bar.bndrun
+
If the filespec
clause does not set the type
nor the name
then it is assumed the backward compatible mode is required. This will set the output name to the file name with a .jar
extension and it will set the bundle symbolic name.
//
+ // EXPORTS
+ //
+ {
+ Set<Instruction> unused = Create.set();
+
+ Instructions filter = new Instructions(getExportPackage());
+ filter.append(getExportContents());
+
+ exports = filter(filter, contained, unused);
+
+ if (!unused.isEmpty()) {
+ warning("Unused " + Constants.EXPORT_PACKAGE + " instructions: %s ", unused);
+ }
+
+ // See what information we can find to augment the
+ // exports. I.e. look on the classpath
+ augmentExports(exports);
+ }
+
+ //
+
The purpose of the -exportreport
instruction is to configure a list of reports of the current state of the workspace and/or its projects, which can then be exported by the build tool. The primary usage is to automate the documentation of projects. An introduction to this feature can be found here.
See -exportreport command documentation to export the reports.
+ +-exportreport ::= report-def ( ',' report-def ) *
+report-def ::= path ( ';' option ) *
+option ::= scope | template | templateType | parameters | locale | configName
+scope ::= 'scope' '=' ( 'workspace' | 'project' | EXTENSION )
+template ::= 'template' '=' ( path | url )
+templateType ::= 'templateType' '=' ( 'xslt' | 'twig' | EXTENSION )
+parameters ::= 'parameters' '=' '"' parameter ( ',' parameter )* '"'
+locale ::= 'locale' '=' <language> ( '-' <country> ( '-' <variant> )? )?
+configName ::= 'configName' '=' extended
+
This is a merged instruction.
+ +The most simple configuration is to generate a file which will contain all the OSGI headers, metatypes declarations, declarative services, … of a bundle built by a project:
+ +bnb.bnd
+ +-exportreport: metadata.json
+
an XML
file can also be generated
-exportreport: metadata.xml
+
The scope
attribute allows to define all the reports in a common place and to target the source of the extracted data. In Bnd, the two possible values are workspace and project but other tools (such as Maven) could define their own scopes.
For example, the following configuration will generate a report at the workspace root, which will aggregate data of all the projects, and one report per project:
+ +build.bnd
+ +-exportreport: \
+ ${workspace}/jekyll_metadata.json;scope=workspace, \
+ metadata.json;scope=project
+
The template
attribute allows to specify a path or an URL to a template file. In Bnd, two template types are accepted: XSLT and TWIG.
For example, a template can be used to generate a markdown file:
+ +bnd.bnd
+ +-exportreport: readme.md;template=/home/me/templates/readme.twig
+
If the extension file is missing, the templateType
attribute can be used to indicate the template type:
bnd.bnd
+ +-exportreport: webpage.html;template=http:<...>/f57ge56a;templateType=xslt
+
If the template file needs to be parametrized, the parameters
attribute can be used to provide a list of parameters and their values:
bnd.bnd
+ +-exportreport: \
+ webpage.html; \
+ template=<...>/template.xslt; \
+ parameters="oneKey=<path to other template>,otherKey=api-bundle"
+
++ +If a file with the same name as the exported report but with a template file extension is found in the same folder as the exported report, this file will be used to transform the report instead of the optionally defined
+template
attribute. This allows to quickly customize a report without redefining an inherited instruction.
The locale
attribute can be used to extract the data for a specific locale. For example, if a bundle defines some metatype description in French:
bnd.bnd
+ +-exportreport: report.json;locale=fr-FR
+
In some case, it may be necessary to control what data should be present in the report, for example if you use external plugins to contribute to the extraction and aggregation phase. For this you can use the -reportconfig
instruction to create named configuration of reports.
With the configName
attribute you can reference the configuration name that will be used to extract and aggregate the data of the report.
See the -reportconfig instruction documentation for more information.
+ +Reports are generated from workspaces and projects. However, in case of sub-projects, it may be necessary to generate a file for each built bundle and to know what bundle must be process by the template engine. For this, you can define a paramater in your template file and provide a different value for each bundle, for example:
+ +build.bnd
+ +-exportreport: \
+ readme.md;template=<...>/readme.twig;scope=project
+
bnd.bnd
+ +-exportreport.sub: \
+ mybundle.api.md; \
+ template=<...>/readme.twig; \
+ parameters="currentBundle=mybundle.api", \
+ mybundle.provider.md; \
+ template=<...>/readme.twig; \
+ parameters="currentBundle=mybundle.provider"
+
For this specific multi-project, this will generate three files:
+ +/**
+ * Add any extensions listed
+ *
+ * @param list
+ * @param rri
+ */
+@Override
+protected void addExtensions(Set<Object> list) {
+ //
+ // <bsn>; version=<range>
+ //
+ Parameters extensions = new Parameters(getProperty(EXTENSION));
+ Map<DownloadBlocker,Attrs> blockers = new HashMap<DownloadBlocker,Attrs>();
+
+ for (Entry<String,Attrs> i : extensions.entrySet()) {
+ String bsn = removeDuplicateMarker(i.getKey());
+ String stringRange = i.getValue().get(VERSION_ATTRIBUTE);
+
+ trace("Adding extension %s-%s", bsn, stringRange);
+
+ if (stringRange == null)
+ stringRange = Version.LOWEST.toString();
+ else if (!VersionRange.isVersionRange(stringRange)) {
+ error("Invalid version range %s on extension %s", stringRange, bsn);
+ continue;
+ }
+ try {
+ SortedSet<ResourceDescriptor> matches = resourceRepositoryImpl.find(null, bsn, new VersionRange(
+ stringRange));
+ if (matches.isEmpty()) {
+ error("Extension %s;version=%s not found in base repo", bsn, stringRange);
+ continue;
+ }
+
+ DownloadBlocker blocker = new DownloadBlocker(this);
+ blockers.put(blocker, i.getValue());
+ resourceRepositoryImpl.getResource(matches.last().id, blocker);
+ }
+ catch (Exception e) {
+ error("Failed to load extension %s-%s, %s", bsn, stringRange, e);
+ }
+ }
+
+ trace("Found extensions %s", blockers);
+
+ for (Entry<DownloadBlocker,Attrs> blocker : blockers.entrySet()) {
+ try {
+ String reason = blocker.getKey().getReason();
+ if (reason != null) {
+ error("Extension load failed: %s", reason);
+ continue;
+ }
+
+ URLClassLoader cl = new URLClassLoader(new URL[] {
+ blocker.getKey().getFile().toURI().toURL()
+ }, getClass().getClassLoader());
+ Enumeration<URL> manifests = cl.getResources("META-INF/MANIFEST.MF");
+ while (manifests.hasMoreElements()) {
+ Manifest m = new Manifest(manifests.nextElement().openStream());
+ Parameters activators = new Parameters(m.getMainAttributes().getValue("Extension-Activator"));
+ for (Entry<String,Attrs> e : activators.entrySet()) {
+ try {
+ Class< ? > c = cl.loadClass(e.getKey());
+ ExtensionActivator extensionActivator = (ExtensionActivator) c.newInstance();
+ customize(extensionActivator, blocker.getValue());
+ List< ? > plugins = extensionActivator.activate(this, blocker.getValue());
+ list.add(extensionActivator);
+
+ if (plugins != null)
+ for (Object plugin : plugins) {
+ list.add(plugin);
+ }
+ }
+ catch (ClassNotFoundException cnfe) {
+ error("Loading extension %s, extension activator missing: %s (ignored)", blocker,
+ e.getKey());
+ }
+ }
+ }
+ }
+ catch (Exception e) {
+ error("failed to install extension %s due to %s", blocker, e);
+ }
+ }
+}
+
@Override
+public boolean isFailOk() {
+ String v = getProperty(Analyzer.FAIL_OK, null);
+ return v != null && v.equalsIgnoreCase("true");
+}
+
The -fixupmessages
instruction is intended to fixup the errors and warnings. It allows you to remove errors and/or warnings, turn errors into warnings, and turn warnings into errors. With this instruction you can fail a build based on a warning or succeed a build that runs into errors.
The default of this instruction is to list a number of patterns. Any error or warning that matches this pattern is then removed. The following example will remove any error/warning that matches 'some error'
, 'another error'
, or 'and yet another error'
.
-fixupmessages: \
+ some error,
+ another error,
+ and yet another error
+
The pattern is a SELECTOR, which makes it possible to do case insensitive matches, wildcards, literals, etc.
+ +The basic format of -fixupmessages
is:
-fixupmessages ::= fixup ( ',' fixup ) *
+fixup ::= SELECTOR directive *
+directive ::= ';' ( restrict | is | replace )
+restrict ::= 'restrict:=' ( 'error' | 'warning' )
+is ::= 'is:=' ( 'ignore' | 'error' | 'warning' )
+replace ::= 'replace:=' <<text>>
+
The value of a fixup clause is a globbing expression.
+ +The following directives are supported:
+ +restrict:
– By default, the fixup clause is applied to all errors and warnings. You can restrict its application to either errors or warnings specifying either restrict:=error
or restrict:=warning
.is:
– By default an error remains an error and a warning remains a warning. However, if you specify the is:
directive you can force an error to become a warning or vice versa. This can be very useful if you build fails with an error that you do not consider a failure.replace:
– Replace the message with a new message. The replacement will be processed by the macro processor, the ${@}
macro will contain the current message.The -fixupmessages
instruction is a merged property. This means that you can define it in many different places like for example in a file in cnf/ext
. Just put an extension on the instruction. For example:
-fixupmessages.one: 'Some error'
+-fixupmessages.two: 'Another error'
+
# Turn an error into a warning
+-fixupmessages
+ "Invalid character'; \
+ restrict:=error;
+ is:=warning
+
+# Replace a message
+-fixupmessages \
+ "split";replace:=broken
+
+# Ignore case by appending :i
+-fixupmessages \
+ "case insensitive:i"
+
+# Wildcards
+-fixupmessages \
+ "prefix*suffix"
+
+# Turn properties parser messages into warnings
+-fixupmessages.parser: \
+ "Invalid character in properties"; \
+ "No value specified for key"; \
+ "Invalid property key"; \
+ "Invalid unicode string"; \
+ "Found \\<whitespace>";
+ is:=warning
+
Virtually all the work bnd is concerned about happens in generating the JAR file. The key idea is to pull resources in the JAR, instead of the more traditional push model of other builders. This works well, except for generating source code. This generating step must happen before the compiler is called, and the compiler is generally called before bnd becomes active.
+ +This -generate
instruction specifies the code generating steps that must be executed. Source code can be generated by system commands or the bnd external plugins.
-generate ::= clause ( ',' clause )*
+clause ::= FILESET ';' 'output=' DIR (';' option )*
+src ::= FILESET
+option ::= 'system=' STRING
+ | 'generate=' STRING
+ | 'classpath=' PATH
+ | 'workingdir=' FILE
+ | 'clear=' BOOLEAN
+ | 'version=' RANGE
+
For each clause, the key of the clause is used to establish an Ant File Set, e.g. foo/**.in
. This a glob expression with the exception that the double wildcard (‘**’) indicates to any depth of directories. The output
attribute must specify a directory. If the output must be compiled this directory must be on the bnd source path.
The output directory will created if it does not exist. It will be cleared of any previous generate results before a run, except if the option clear
is set to false. In this case the used Generator needs to deal with the remnants in the directory itself.
If any file in the source is older than any file in the target (to any depth), or the target is empty, the clause is considered stale. If the clause is not stale, it is further ignored. If no further options are set on the clause, a warning is generated that some files are out of date.
+ +If either a warning or error option is given, these will be executed on the project.
+ +If a command STRING
is given it is executed as in the ${system}
macro. If the command STRING
starts with a
+minus sign (-
) then a failure is not an error, it is reported as warning.
The generate option will execute an external plugin or plain JAR with a Main-Class
manifest header. The choice is made by looking at the first word in the generate
attribute.
If this name has a dot in it, like in a fully qualified class name, it is assume that species a class name. (If the name starts with a dot, it will be assume to be a name in the default package.) In this case, the classpath
attribute of the instruction can be used to provide additional JARs on the command’s classpath. The format of PATH is the standard format for instructions like -buildpath. In this case, you can also set the workingdir
to a directory. This directory is specified relative to the project.
Without a dot in the name, the name is assumes to be an external plugin name, with the objectClass
(service type) of Generator<? extends Options>
. External , or Main-Class jars, can come from an external repository or a local workspace project.
The generate
value is a command line. It can use the standard unix like way of specifying a command. It supports flags (boolean parameters) and parameter that take a value. When this external plugin is executed, it is expected to create files fall within the target, if not, an error is reported. These changed or created files are refreshed in Eclipse.
The command line can be broken in different commands with the semicolon (';'
), like a unix shell. Redirection of stdin ('<'
), stdout ('>'
, or '1>'
), and stderr ('2>'
) are supported. The path for redirection is relative to the project directory, even if workingdir
has been specified.
With the version
version range attribute it is possible to restrict the candidates if there are multiple versions available. The code will select the highest version if only one is used.
Include in the bnd build is a javagen external plugin that is useful to generate Java code based on build information. It uses a template directory with Java files. When the external plugin runs, it will use all these files to write matching Java files in the output directory, in a matching package directory. The input Java files can be prefixed with a properties header:
+ +---
+foo: 1
+---
+package foo.bar;
+class Foo {
+ int foo = ${foo};
+}
+
The optional header is removed and then the remainder of the file is ran through the bnd macro processor.
+ +If this example is used, it is necessary to add a new source folder. In Eclipse, this requires adding an entry in the .classpath
file, in bnd it requires the modification of the src
property.
src=${^src},src-gen
+
Assuming that the input files are in the gen
directory, the following can be used to automatically generate the output files based on the input files.
-generate: \
+ gen/**.java; \
+ output='src-gen/' ; \
+ generate='javagen -o src-gen gen/'
+
JFlex and CUP are popular tools to create lexers and parsers. The JARs are on Maven Central with Main-Class manifest attribute. The JFlex JAR, however, requires the cup_runtime
JAR on the classpath.
We can directly use these executable JARs with the -generate
instruction.
-generate: \
+ lex/Foo.lex; \
+ output = gen-src/ \
+ generate = `jflex.Main -d lex-gen/ lex/Foo.lex'; \
+ classpath = 'de.jflex:cup_runtime;version=11b-20160615'
+
Notice that we use the maven
GAV format here to find the cup_runtime
because these JARs are unfortunately not bundles.
It is possible to create an External Plugin or a Main Class command your in the same workspace as where you apply the command. This makes it easy to develop commands interactively. The easiest way is to make an External Plugin. The support for Main-Class is mostly to support existing JAR.
+ +The following is an Generate external plugin that outputs “hello”:
+ +@ExternalPlugin(name = "hello", objectClass = Generator.class)
+public class Hello implements Generator<HelloOptions> {
+
+ public interface HelloOptions extends Options {
+ boolean upper();
+ String name(String defaultName)
+ }
+
+ public Optional<String> generate(
+ BuildContext context,
+ HelloOptions options) throws Exception {
+ String hello = "Hello " + options.name("World");
+ if ( options.upper() )
+ hello = hello.toUpperCase();
+
+ System.out.println( hello );
+ return Optional.empty();
+ }
+}
+
The ` @ExternalPlugin` annotation creates an external plugin capability in the bundle’s manifest. The name is the name we can use as the command name. Do not use a name that has dots in it, this will then be confused with a main class command.
+ +The Hello class implements Generator<HelloOptions>
. This is the type the generate code expects.
The type parameter specifies a specification interface. This interface is used to specify the command line. A boolean
method is a flag, and any other type is an option. The first character of the method is the name of the flag or option. For example, boolean upper()
is a flag and has the -u
and --upper
. Flags can be combined. For example, if you have a flag -a
and -b
then you can also use -ab
.
Options take parameters. For example, a String name()
option would be set as -n World
. The method on the spec interface can optionally take an argument, this argument is used as default if the option is not used in the command line. The argument type and the return type can be different. For example, if the return type is File
, then the parameter type can be String
so that the returned File
is resolved against the base directory of the build.
Options that return File
will resolve the input against the project directory. A Set
The Options interface specifies a number of additional keys:
+ +The actual code is in the generate(BuildContext,HelloOptions)
method. the Build Context contains useful context information.
It is ok to write to the System.out and System.err. The output is captured and can be redirected in the command line in the standard Unix way.
+ +The code is expected to return an Optional.empty()
when everything is ok. If something is wrong, an actualString can be used to explain the error. Return a non-empty fails the call and the error will be reported.
If you the plugin source code is in the same workspace as the project using this plugin, you must make sure that the external plugin project is build before the project that uses it. You can achieve this with -dependson.
+ +You can take a look at the JavaGen project in the bnd build to see how an actual external plugin is made.
+ + +The -groupid
instruction defines the default Maven groupId to be used when generating POM resources and releasing to the Maven Bnd Repository Plugin.
If not specified, the value of -groupid
defaults to the Bnd workspace folder name.
Also see the -pom and -maven-release instructions.
+ + +You can use -include
as follows:
-include: <path or url>
+
This will read the path or url as a properties or manifest file (if it ends in .MF
).
It is important to realize that the include is not handled by the parser.
+That is, it is not a normal text include.
+The properties parser will read all properties in one go and then the Properties object is inspected for the -include
instruction.
+The paths or URLs in the -include
instruction are processed one by one in order.
+By default, the properties in the included file overwrite the properties that were set in the same file as the -include
instruction.
+If a property is already defined and not set to be overwritten (see below), the property will get a namespace assigned.
+The namespace will be derived from the filename or the last segment of the URL.
The -include
instruction is processed before anything else in the properties.
+This means the -include
instruction cannot use any properties defined in the same properties file to define the include paths.
+It can use properties already defined by a parent.
+So a Project’s bnd.bnd
file can have an -include
instruction use properties defined in the Workspace (cnf/build.bnd
and cnf/ext/*.bnd
).
+For the Workspace, cnf/build.bnd
, -include
instruction can only use the default properties of Bnd.
+The Workspace cnf/ext/*.bnd
files are processed after cnf/build.bnd
.
+So cnf/ext/*.bnd
files can have -include
instructions which use properties set in cnf/build.bnd
.
There are two possible options. The path/URL starts with a:
+ +~
– Included properties do not overwrite any existing properties having the same property names.-
– If file or URL or path does not exist then do not report an error.# Read an optional file in the user's home directory
+-include ${user.home}/.xyz/base.bnd
+
+# Read a manifest
+-include META-INF/MANIFEST.MF
+
+# Use a URL
+-include https://example.com/foo/bar/setup.bnd
+
+# Read several
+-include first.bnd, second.properties
+
+# Don't overwrite any existing properties
+-include ~no.overwrite
+
The -includepackage
instruction falls in the family of instructions to pull in packages from the current class path. The
+instructions lists one of more package specifications that can contain wildcards. Any attributes or directives are
+ignored. This instruction only operates during the expand phase of the JAR. It has not further semantics.
The primary motivation for this instruction is the use of the @Export
annotation. Using that annoation puts
+bnd in a bind. A Private-Package should be private and an Export-Package should be exported. Since a bnd file
+should always have the final say, the @Export
instruction needed an instruction that took in the packages but
+that did not put export semantics on it.
This instruction is also taken into account when taken the decision to automatically fill up the JAR from the +current project. If this instruction is set (or any other construction instruction) it is assumed that the user +wants to be in control and the instructions are followed. If none of these instructions is set then the JAR is +constructed from the project’s output folder.
+ +When the packages selected by the -includepackage
instructions overlap with either Private-Package or Export-Package
+then the Export-Package has the highest priority and then Private-Package. That is, any @Export
in a package that is selected
+by Export-Package or Private-Package is ignored silently.
Notice that an annotation like @Version
in a a package like selected can still provide the version if the Export-Package
+header for that package provides no version.
The following example includes all packages from com.example.*
except com.example.bar
.
-includepackage !com.example.bar, com.example.*
+
The purpose of -includeresource
is to fill the JAR with non-class resources. In general these come from the file system. For example, today it is very common to have these type of resources in src/main/resources
. This pattern can easily be simulated by bnd with the -includeresource
instruction. However, since in OSGi the packaging is so important the -includeresource
contains a number of options to minimize files on disk and speed up things.
The syntax of the -includeresource
has become quite complex over time:
-includeresource ::= iclause ( ',' iclause ) *
+iclause ::= (unroll | copy) parameter*
+copy ::= '{' process '}' | process
+process ::= assignment | source
+assignment ::= PATH '=' source
+source ::= ('-')? PATH parameter*
+unroll ::= '@' (PATH | URL) ( '!/' SELECTOR )?
+parameters ::= 'flatten:' | 'recursive:' | 'filter:' | `-preprocessmatchers`
+
In the case of assignment
or source
, the PATH parameter can point to a file or directory. It is also possible to use the name.ext path of a JAR file on the classpath, that is, ignoring the directory. The source
form will place the resource in the target JAR with only the file name, therefore without any path components. That is, including src/a/b.c
will result in a resource b.c
in the root of the target JAR.
If the PATH points to a directory, the directory name itself is not used in the target JAR path. If the resource must be placed in a sub directory of the target jar, use the assignment
form. If the file is not found, bnd will traverse the classpath to see of any entry on the classpath matches the given file name (without the directory) and use that when it matches. The inline
requires a ZIP or JAR file, which will be completely expanded in the target JAR (except the manifest), unless followed with a file specification. The file specification can be a specific file in the jar or a directory followed by ** or *. The ** indicates recursively and the * indicates one level. If just a directory name is given, it will mean **.
The filter:
directive is an optional filter on the resources. This uses the same format as the instructions. Only the file name is verified against this instruction.
Include-Resource: @osgi.jar,[=\ =]
+ {LICENSE.txt},[=\ =]
+ acme/Merge.class=src/acme/Merge.class
+
The -includeresources
instruction will be merged with all properties that starts with -includeresources*
.
A clause contained in curly braces ({
}
) are preprocessed. While copying the files are run through the macro processor with the builder providing the properties. In the workspace model, all macros of the project are then available. Well known binary resources (as decided by their extension) are ignored. You can override the extension list with the -preprocessmatchers
instruction. This must be a a selector that takes the source file name as the input. The clause can also specify a local -preprocessmatchers
. This selector is prepended to the either the default pre process matchers or the set pre process matchers. This allows for the selection or rejection of specific files and/or extensions.
-includeresource: {src/main/resources}, {legal=contracts}
+
A source in the clause starting with a -
sign will not generare an error when the source in the clause cannot be located. This is very convenient if you specify an global -includeresource
instruction in build.bnd
. For example, -includeresource.all = -src/main/resources
will not complain when a project does not have a src/main/resources
directory. Note that the minus sign must be on the source. E.g.
`-includeresource.all = {foo=-bar}`, -foo.txt
+
Unrolling is getting the content from another JAR. It is activated by starting the source with an at sign (@
). The at sign signals that it is not the actual file that should be copied, but the contents of that file should be placed in the destination.
-includeresource tmp=@jar/foo.jar
+
The part that follows the at sign (@
) is either a file path or a URL. Without any extra parameters it will copy all resources except the ones in the -donotcopy
list and the META-INF/MANIFEST
.
-includeresource @jar/foo.jar
+
This is an ideal way to wrap a bundle since it is a full copy. After that one can add additional resources or use -exportcontents
to export the contained packages in the normal way. In this way, bnd will calculate all imports.
The unrolling can also be restricted with a single selector. The syntax for the selector must start with a !/
marker, which is commonly used for this purpose. After the !/
the normal selector operators and patterns can be used. For example, if we want to get just the LICENSE
from a bundle then we can do:
-includeresource @jar/foo.jar!/LICENSE
+
However, since selectors can also negate, it is also possible to do the reverse:
+ +-includeresource "@jar/foo.jar!/!LICENSE"
+
This is a single selector, it is therefore not possible to specify a chain with rejections and selections. However, also a single selector can match multiple file paths:
+ +-includeresource @jar/osgi.jar!/!(LICENSE|about.html|org/*)
+
Wrapping often requires access to a JAR from the repository. It is therefore common to see the unrolling feature being combined with the ${repo}
macro. The ${repo}
macro is given the Bundle Symbolic Name and an optional version range returns the path to a file from the repository.
-includeresource @${repo;biz.aQute.bndlib}!/about.html
+
flatten:=BOOLEAN
- puts all files in the file-tree into one folder
-includeresource new.package/=@jar/file.jar!/META-INF/services/*;flatten:=true
+
rename:=RENAME
- maps the path using a given renaming instruction. Paths are filtered by the given instruction-SELECTOR. The instruction-Selector is compiled to a regex-pattern. This pattern is used to generate a matcher by using the filtered path and the matcher is used to replaceAll using the given extra value.
-includeresource new.package=@jar/cxf-rt-rs-sse-3.2.5.jar!/(META-INF)/(c*f)/(*);rename:=$2/$1/$3.copy
+
For testing purposes it is often necessary to have tiny resources in the bundle. These could of course be placed on the file system but bnd can also generate these on the fly. Since these are defined in the bnd files, the content has full access to the macros. This is done by specifying a literal
attribute on the clause.
-includeresource foo.txt;literal='This is some content with a macro ${sum;1,2,3}'
+
The previous example will create a resource with the given content.
+ +When a directory is specified bnd will by default recurse the source and create a similar hierarchy on the destination.
+ +The recursion and the hierarchy can be controlled with directives.
+ +-includeresource target/=hierarchy/
+
The recursive:
directive can be used to indicate that the source should not be recursively traversed by specifying false
:
-includeresource target/=hierarchy/;recursive:=false
+
In this case, only the hierarchy
directory itself will be copied to the target
directory. The flatten:
directive indicates that if the directories are recursively searched, the output must not create any directories. That is all resources are flattened in the output directory.
-includeresource target/=hierarchy/;flatten:=true
+
Instruction | +Explanation | +
---|---|
-includeresource: lib/fancylibrary-3.12.0.jar |
+ Copy lib/fancylibrary-3.12.0.jar file into the root of the target JAR | +
-includeresource.resources: -src/main/resources |
+ Copy folder src/main/resources contents (including subdfolders) into root of the target JAR The arbitrarily named suffix .resources prevents this includeresource directive to be overwritten The preceding minus sign instructs to supress an error for non-existing folder src/main/resources |
+
-includeresource: ${workspace}/LICENSE, {readme.md} |
+ Copy the LICENSE file residing in the bnd workspace folder (above the project directory) as well as the pre-processed readme.md file (allowing for e.g. variable substitution) in the project folder into the target JAR | +
-includeresource: ${repo;com.acme:foo;latest} |
+ Copy the com.acme.foo bundle JAR in highest version number found in the bnd workspace repository into the root of the target JAR | +
Instruction | +Explanation | +
---|---|
-includeresource: images/=img/ or -includeresource: images=img |
+ Copy contents of img/ folder (including subdfolders) into an images folder of the target JAR | +
-includeresource: x=a/c/c.txt |
+ Copy a/c/c.txt into file x in the root folder of the target JAR | +
-includeresource: x/=a/c/c.txt |
+ Copy a/c/c.txt into file x/c.txt in the root folder of the target JAR | +
-includeresource: libraries/fancylibrary.jar=lib/fancylibrary-3.12.jar; lib:=true |
+ Copy lib/fancylibrary-3.1.2.jar from project into libraries folder of the target JAR, and place it on the Bundle-Classpath (BCP). It will make sure the BCP starts with ‘.’ and then each include resource that is included will be added to the BCP | +
-includeresource: lib/; lib:=true |
+ Copy every JAR file underneath lib in a relative position under the root folder of the target JAR, and add each library to the bundle classpath | +
-includeresource: acme-foo-snap.jar=${repo;com.acme:foo;snapshot} |
+ Copy the highest snapshot version of com.acme.foo found in the bnd workspace repository as acme-foo-snap.jar into the root of the target JAR | +
-includeresource: foo.txt;literal='foo bar' |
+ Create a file named foo.txt containing the string literal “foo bar” in the root folder of the target JAR | +
-includeresource: bsn.txt;literal='${bsn}' |
+ Create a file named bsn.txt containing the bundle symbolic name (bsn) of this project in the root folder of the target JAR | +
-includeresource: libraries/=lib/;filter:=fancylibrary-*.jar;recursive:=false;lib:=true or -includeresource: libraries/=lib/fancylibrary-*.jar;lib:=true (as of bndtools 4.2) |
+ Copy a wildcarded library from lib/ into libraries and add it to the bundle classpath | +
Instruction | +Explanation | +
---|---|
-includeresource: @lib/fancylibrary-3.12.jar!/* |
+ Extract the contents of lib/fancylibrary-3.12.jar into the root folder of the target JAR, preserving relative paths | +
-includeresource: @${repo;com.acme.foo;latest}!/!META-INF/* |
+ Extract the contents of the highest found com.acme.foo version in the bnd workspace repository into the root folder of the target JAR, preserving relative paths, excluding the META-INF/ folder | +
/**
+ * Verify of the path names in the JAR are valid on all OS's (mainly
+ * windows)
+ */
+void verifyPathNames() {
+ if (!since(About._2_3))
+ return;
+
+ Set<String> invalidPaths = new HashSet<String>();
+ Pattern pattern = ReservedFileNames;
+ setProperty("@", ReservedFileNames.pattern());
+ String p = getProperty(INVALIDFILENAMES);
+ unsetProperty("@");
+ if (p != null) {
+ try {
+ pattern = Pattern.compile(p, Pattern.CASE_INSENSITIVE);
+ }
+ catch (Exception e) {
+ SetLocation error = error("%s is not a valid regular expression %s: %s", INVALIDFILENAMES,
+ e.getMessage(), p);
+ error.context(p).header(INVALIDFILENAMES);
+ return;
+ }
+ }
+
+ Set<String> segments = new HashSet<String>();
+ for (String path : dot.getResources().keySet()) {
+ String parts[] = path.split("/");
+ for (String part : parts) {
+ if (segments.add(part) && pattern.matcher(part).matches()) {
+ invalidPaths.add(path);
+ }
+ }
+ }
+
+ if (invalidPaths.isEmpty())
+ return;
+
+ error("Invalid file/directory names for Windows in JAR: %s. You can set the regular expression used with %s, the default expression is %s",
+ invalidPaths, INVALIDFILENAMES, ReservedFileNames.pattern());
+}
+
+
+
+public final static Pattern ReservedFileNames = Pattern
+ .compile(
+ "CON(\\..+)?|PRN(\\..+)?|AUX(\\..+)?|CLOCK$|NUL(\\..+)?|COM[1-9](\\..+)?|LPT[1-9](\\..+)?|"
+ + "\\$Mft|\\$MftMirr|\\$LogFile|\\$Volume|\\$AttrDef|\\$Bitmap|\\$Boot|\\$BadClus|\\$Secure|"
+ + "\\$Upcase|\\$Extend|\\$Quota|\\$ObjId|\\$Reparse",
+ Pattern.CASE_INSENSITIVE);
+
If the value is true, then each classpath elements that
+has a Premain-Class
manifest header is used as a java agent
+when launching by adding a -javaagent:
argument to the java invocation.
-javaagent:jarpath
+
If the classpath element was specified with an agent
attribute, the
+value of the agent
attribute will be used as the options for the
+-jaragent:
argument to the java invocation.
-javaagent:jarpath=options
+
See jpms for an overview and the detailed rules how the module-info.class
file is
+calculated.
The -jpms-module-info-options
instruction provides some capabilities to help the developer handle these scenarios. The keys of these instructions are module names and there are 4 available attributes.
-jpms-module-info-options ::= moduleinfo
+moduleinfo ::= NAME
+ [ ';substitute=' substitute ]
+ [ ';ignore=' ignore ]
+ [ ';static=' static ]
+ [ ';transitive=' transitive ]
+
They attributes are:
+ +substitute
- If bnd generates a module name matching the value of this attribute it should be substituted with the key of the instruction.
+e.g.
-jpms-module-info-options: java.enterprise;substitute=”geronimo-jcdi_2.0_spec”
+ +means that if bnd calculates the module name to be geronimo-jcdi_2.0_spec
it should replace it with java.enterprise
– ignore
- If the attribute ignore="true"
is found the require matching the key of the instruction will not be added.
+ e.g.
-jpms-module-info-options: java.enterprise;ignore="true"
+
means ignore the module java.enterprise
static
- If the attribute static="true|false"
is found the access of the module matching the key of the instruction will be set to match.
+e.g.
-jpms-module-info-options: java.enterprise;static="true"
+
means make the require
for module java.enterprise
static
transitive
- If the attribute transitive="true|false"
is found the access of the module matching the key of the instruction will be set to match.
+e.g.
-jpms-module-info-options: java.enterprise;transitive="true"
+
means make the require
for module java.enterprise
transitive
The following is an example with multiple attributes and instructions:
+ +-jpms-module-info-options: \
+ java.enterprise;substitute="geronimo-jcdi_2.0_spec";static=true;transitive=true,\
+ java.management;ignore=true;
+
See jpms for an overview and the detailed rules how the module-info.class
file is
+calculated.
The -jpms-module-info
instruction is a single parameter
-jpms-module-info ::= module-name [ ';version=' VERSION ] access
+access ::= `;access=' '"' item ( ',' item ) * '"'
+item ::= 'OPEN' | 'SYNTHETIC' | 'MANDATED'
+
Automatic-Module-Name
is used, or if that one is not set, the Bundle-SymbolicName.version
– The version, otherwise the bundle version is used.access
– The access flags. These indicate the access mode of the module.For example:
+ +-jpms-module-info: foo.module;version=5.4.1; access="OPEN,SYNTHETIC"
+
This instruction controls that if a JAR setup to be a multi-release jar the manifests & module-infos
+for each supported versions should be added. If this instruction is true, it will generate this metadata,
+if the instruction is absent or the value is not true, then it will ignore the versions
.
A multi release Jar (MRJ) will contain directories in META-INF/versions/
, where the directory name is a release
+number. If this instruction is enabled, then during manifest generation, bnd will also calculate a manifest and
+module-info in each versioned directory.
This instruction has as purpose to collect options and special settings for the launcher. The +following options are architected:
+ +manage
– Indicates to the launcher how to treat unrecognized bundles. When the launcher starts
+it gets a list of run bundles, also called its scope. However, previous runs could’ve installed
+other bundles that do not occur in the scope. By default the launcher should manage all
+bundles. However, sometimes these bundles were installed by an agent and the launcher should
+not touch them. Therefore the values for the ‘manage’ part are:
+ all
– This is the default and the launcher assumes it owns all the bundlesnarrow
– The launcher will only touch the bundles that are part of its scopenone
– The launcher should defer from managing any bundles.-launcher manage = all
+
This instruction was primarily designed to handle start levels. Originally the launcher was +narrowly managing only the bundles that were in its scope. However, this was inadvertently +changed and not discovered for several reasons. The option to narrowly manage was therefore +introduced with the default being the latest behavior.
+ + +A library can provide additional named functionality to a workspace, a project, or a bndrun file. It does this by
+including a bnd
file in the setup that originates from a bundle in the repositories. This included bnd file
+can refer and use any binary resources in this bundle. Bundles can contain multiple libraries.
The -library
instruction can apply a library extension from the repositories to a workspace or project. A library
+extension is a resource that is stored in one of the repositories. When the -library
instruction is used, the corresponding resource
+will be expanded in the workspace’s cache and one or more files from this area are read as include files containing
+bnd properties.
-library ::= library ( ',' library )*
+library ::= '-'? NAME ( ';' parameter ) *
+
For example, the library foo
is included in a resource in the repository. We can apply this library in the
+build.bnd
, bnd.bnd
, or *.bndrun
file.
-library foo
+
The following parameter
attributes are architected:
* version
– The version range of the capability. If no version is specified, 0 is used. If a version is specified, then
+ this is the lowest acceptable version. The runtime will select the highest matching version. The version
can also be:
file
– the library name is a path to a directory or JAR file. Since this lacks the ‘where’ of the library in the
+directory or JAR, this can be set with the where
attribute in the same clause.include
– The name of the include file or directory, relative to the root directory of the library. If a directory
+is targeted, all *.bnd
files will be read. The default include depends on where the library is included. In:
+ build.bnd
) – workspace.bnd
bnd.bnd
) – project.bnd
*.bndrun
) – bndrun.bnd
Some remarks:
+ +-
, there will be no error if the capability cannot be found.${.}
macro refers to the cached root directory of the library.Since libraries are stored in repositories but can also provide new repositories it is important to +understand the ordering.
+ +Only libraries included in the workspace can contribute repositories. The workspace will first read the cnf/ext
+directory bnd files and then build.bnd
. Any repositories defined in these bnd files can be used for libraries.
+The workspace will include the libraries based on these repositories. However, after all libraries have
+been included, all plugins are reset and this will reset the repositories. Any repository plugins defined
+in a library will then become available.
Projects and bndrun files can include libraries but they cannot define any new repositories.
+ +NOTE: what about standalone bndruns?
+ +A library is stored in a bundle. A bundle can contain multiple libraries that are each described by a
+capability in the bnd.library
name space. This capability looks like:
Provide-Capability: \
+ bnd.library; \
+ bnd.library = foo; \
+ version = 1.2.3; \
+ path = lib/foo
+
The following attributes are defined for the bnd.library
capabilities:
bnd.library
– The name of the library. Names should be kept simple but the names are shared between all libraries.version
– The version of the library.path
– A path to a directory in the bundle. The contents of this directory are the root of the library. This root
+is copied to the workspace cache.The root of the library should contain the bnd files to be included. The defaults (workspace.bnd
, project.bnd
, and
+bndrun.bnd
) should only be there if it makes sense to include the library in that type.
package aQute.bnd.make;
+
+ import java.util.*;
+ import java.util.Map.Entry;
+ import java.util.regex.*;
+
+ import aQute.bnd.header.*;
+ import aQute.bnd.osgi.*;
+ import aQute.bnd.service.*;
+
+ public class Make {
+ Builder builder;
+ Map<Instruction,Map<String,String>> make;
+
+ public Make(Builder builder) {
+ this.builder = builder;
+ }
+
+ public Resource process(String source) {
+ Map<Instruction,Map<String,String>> make = getMakeHeader();
+ builder.trace("make " + source);
+
+ for (Map.Entry<Instruction,Map<String,String>> entry : make.entrySet()) {
+ Instruction instr = entry.getKey();
+ Matcher m = instr.getMatcher(source);
+ if (m.matches() || instr.isNegated()) {
+ Map<String,String> arguments = replace(m, entry.getValue());
+ List<MakePlugin> plugins = builder.getPlugins(MakePlugin.class);
+ for (MakePlugin plugin : plugins) {
+ try {
+ Resource resource = plugin.make(builder, source, arguments);
+ if (resource != null) {
+ builder.trace("Made " + source + " from args " + arguments + " with " + plugin);
+ return resource;
+ }
+ }
+ catch (Exception e) {
+ builder.error("Plugin " + plugin + " generates error when use in making " + source
+ + " with args " + arguments, e);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private Map<String,String> replace(Matcher m, Map<String,String> value) {
+ Map<String,String> newArgs = Processor.newMap();
+ for (Map.Entry<String,String> entry : value.entrySet()) {
+ String s = entry.getValue();
+ s = replace(m, s);
+ newArgs.put(entry.getKey(), s);
+ }
+ return newArgs;
+ }
+
+ String replace(Matcher m, CharSequence s) {
+ StringBuilder sb = new StringBuilder();
+ int max = '0' + m.groupCount() + 1;
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c == '$' && i < s.length() - 1) {
+ c = s.charAt(++i);
+ if (c >= '0' && c <= max) {
+ int index = c - '0';
+ String replacement = m.group(index);
+ if (replacement != null)
+ sb.append(replacement);
+ } else {
+ if (c == '$')
+ i++;
+ sb.append(c);
+ }
+ } else
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ Map<Instruction,Map<String,String>> getMakeHeader() {
+ if (make != null)
+ return make;
+ make = Processor.newMap();
+
+ String s = builder.getProperty(Builder.MAKE);
+ Parameters make = builder.parseHeader(s);
+
+ for (Entry<String,Attrs> entry : make.entrySet()) {
+ String pattern = Processor.removeDuplicateMarker(entry.getKey());
+
+ Instruction instr = new Instruction(pattern);
+ this.make.put(instr, entry.getValue());
+ }
+
+ return this.make;
+ }
+ }
+
// Check if we override the calculation of the
+ // manifest. We still need to calculated it because
+ // we need to have analyzed the classpath.
+
+ Manifest manifest = calcManifest();
+
+ String mf = getProperty(MANIFEST);
+ if (mf != null) {
+ File mff = getFile(mf);
+ if (mff.isFile()) {
+ try {
+ InputStream in = new FileInputStream(mff);
+ manifest = new Manifest(in);
+ in.close();
+ }
+ catch (Exception e) {
+ error(MANIFEST + " while reading manifest file", e);
+ }
+ } else {
+ error(MANIFEST + ", no such file " + mf);
+ }
+ }
+
public Jar build() throws Exception {
+ trace("build");
+ init();
+ if (isTrue(getProperty(NOBUNDLES)))
+ return null;
+
+ if (getProperty(CONDUIT) != null)
+ error("Specified " + CONDUIT + " but calls build() instead of builds() (might be a programmer error");
+
+ Jar dot = new Jar("dot");
+ try {
+ long modified = Long.parseLong(getProperty("base.modified"));
+ dot.updateModified(modified, "Base modified");
+ }
+ catch (Exception e) {
+ // Ignore
+ }
+ setJar(dot);
+
+ doExpand(dot);
+ doIncludeResources(dot);
+ doWab(dot);
+
+ // Check if we override the calculation of the
+ // manifest. We still need to calculated it because
+ // we need to have analyzed the classpath.
+
+ Manifest manifest = calcManifest();
+
+ String mf = getProperty(MANIFEST);
+ if (mf != null) {
+ File mff = getFile(mf);
+ if (mff.isFile()) {
+ try {
+ InputStream in = new FileInputStream(mff);
+ manifest = new Manifest(in);
+ in.close();
+ }
+ catch (Exception e) {
+ error(MANIFEST + " while reading manifest file", e);
+ }
+ } else {
+ error(MANIFEST + ", no such file " + mf);
+ }
+ }
+
+ if ( !isTrue(getProperty(NOMANIFEST))) {
+ dot.setManifest(manifest);
+ String manifestName = getProperty(MANIFEST_NAME);
+ if (manifestName != null)
+ dot.setManifestName(manifestName);
+ } else {
+ dot.setDoNotTouchManifest();
+ }
+
+ // This must happen after we analyzed so
+ // we know what it is on the classpath
+ addSources(dot);
+
+ if (getProperty(POM) != null)
+ dot.putResource("pom.xml", new PomResource(dot.getManifest()));
+
+ if (!isNoBundle())
+ doVerify(dot);
+
+ if (dot.getResources().isEmpty())
+ warning("The JAR is empty: The instructions for the JAR named %s did not cause any content to be included, this is likely wrong",
+ getBsn());
+
+ dot.updateModified(lastModified(), "Last Modified Processor");
+ dot.setName(getBsn());
+
+ doDigests(dot);
+
+ sign(dot);
+ doSaveManifest(dot);
+
+ doDiff(dot); // check if need to diff this bundle
+ doBaseline(dot); // check for a baseline
+
+ String expand = getProperty("-expand");
+ if ( expand != null) {
+ File out = getFile(expand);
+ out.mkdirs();
+ dot.expand(out);
+ }
+ return dot;
+}
+
The -pom instruction can be used to generate a pom in the bundle. The value of the -maven-dependencies
instruction is used to generate the <dependencies>
section in the generated pom.
-maven-dependencies ::= entry ( ',' entry ) *
+ entry ::= key ( ';' attribute ) *
+ attribute ::= groupId-attr | artifactId-attr | version-attr
+ | classifier-attr
+ groupId-attr ::= 'groupId' '=' groupId
+ artifactId-attr ::= 'artifactId' '=' artifactId
+ version-attr ::= 'version' '=' version
+ classifier-attr ::= 'classifier' '=' classifier
+ gavkey ::= groupId ':' artifactId ':' version ( ':jar:' classifier)?
+
For Bnd Workspace builds, the -maven-dependencies
instruction is automatically set, if not already set, by Bnd from the -buildpath entries of the project. Normally, you can allow Bnd to automatically set the -maven-dependencies
instruction. But you can override the maven dependency information by explicitly setting the -maven-dependencies
instruction. If set to the empty string, then no <dependencies>
section will be added in the generated pom. Since the -maven-dependencies
instruction is a merged instruction, you can use suffixes to override the generated information for a dependency and to add additional dependencies to the maven dependency information.
When Bnd automatically sets the -maven-dependencies
instruction, it will generate the key
value using the gavkey
production with the groupId, artifactId, version, and classifier (if set) values of the artifact. Using a well defined gavkey
value, allows the attributes of a specific artifact to be overriden by merged property value while allowing the other automatically set values to still be used. However any unique key
value can be used if you don’t care to override the automatically set value for a specific dependency.
When an artifact come from a maven repository, such as MavenBndRepository or BndPomRepository, the maven repository will supply the groupId
, artifactId
, version
, and classifier
(if the artifact has a non-empty classifier) attributes of the artifact. If the artifact does not come from a maven repository but does contain a pom.properties resource, then the groupId
, artifactId
, and version
attributes are supplied by that resource.
An attribute value can also be specified on a -buildpath entry, by prefixing the attribute name with maven-
, which can then replace any such attribute value coming from the maven repository or a pom.properties resource.
You can set the maven-scope
attribute on a -buildpath entry to specify a different dependency scope than the default dependency scope specified by the -maven-scope instruction. The value of the maven-scope
attribute must be a valid dependency scope.
You can also set the maven-optional
attribute on a -buildpath entry to true
which will exclude the artifact from the generated dependencies information. The default value for maven-optional
is false
.
Disable generating the <dependencies>
section in the generated pom.
-maven-dependencies:
+
Override the automatically set dependency information for a specific artifact. You must use the matching gavkey
of the actual artifact and then specify the groupId
, artifactId
, and version
attributes to use for the artitact. This example replaces the version of the artifact to remove -SNAPSHOT
.
-maven-dependencies.fix: org.osgi:osgi.annotation:8.0.0-SNAPSHOT;\
+ groupId=org.osgi;artifactId=osgi.annotation;version=8.0.0
+
This could also be done on the -buildpath entry for the artifact by specifying the maven-version
attribute to override the maven version of the artifact.
-buildpath: osgi.annotation;version=8.0.0.SNAPSHOT;maven-version=8.0.0
+
Add an additional dependency. You must specify the groupId
, artifactId
, and version
attributes to use for the artitact.
-maven-dependencies.log: log;groupId=org.osgi;\
+ artifactId=org.osgi.service.log;version=1.5.0
+
Don’t generate a <dependency>
element for a specific artifact. This is done by specifying the proper gavkey
value and not specifying any attributes.
-maven-dependencies.nodependency: org.osgi:osgi.annotation:8.0.0
+
This could also be done on the -buildpath entry for the artifact by specifying the maven-optional
attribute with the value true
.
-buildpath: osgi.annotation;version=8.0.0;maven-optional=true
+
Change the dependency scope for the artifact. The default dependency scope is specified by the -maven-scope instruction.
+ +-buildpath: osgi.annotation;version=8.0.0;maven-scope=provided
+
The -maven-release
instruction provides the context for a release to Maven repository. In the Maven world it is customary that a release has a JAR with sources and a JAR with Javadoc. In the OSGi world this is unnecessary because the sources can be packaged inside the bundle. (Since the source is placed at a standard location, the IDEs can take advantage of this.) However, putting an artifact on Maven Central requires that these extra JARs are included. This instruction allows you to specify additional parameters for this release process.
Though this instruction is not specific for a plugin, it was developed in conjunction with the Maven Bnd Repository Plugin.
+ +-maven-release ::= ( 'local'|'remote' ( ';' snapshot )? ) ( ',' option )*
+snapshot ::= <value to be used for timestamp>
+option ::= sources | javadoc | pom | sign | archive*
+archive ::= 'archive'
+ ( ';path=' ( PATH | '{' PATH '}' )?
+ ( ';classifier=' maven-classifier )?
+sources ::= 'sources'
+ ( ';path=' ( 'NONE' | PATH ) )?
+ ( ';force=' ( 'true' | 'false' ) )?
+ ( ';-sourcepath=' PATH ( ',' PATH )* )?
+javadoc ::= 'javadoc'
+ ( ';path=' ( 'NONE' | PATH ) )?
+ ( ';packages=' ( 'EXPORTS' | 'ALL' ) )?
+ ( ';force=' ( 'true' | 'false' ) )?
+ ( ';' javadoc-option )*
+javadoc-option ::= '-' NAME '=' VALUE
+pom ::= 'pom'
+ ( ';path=' ( 'JAR' | PATH ) )?
+sign ::= 'sign'
+ ( ';passphrase=' VALUE )?
+
If sources
or javadoc
has the attribute force=true
, either one will be release to the maven repository even if no releaseUrl
or snapshotUrl
is set or maven-release=local
.
The aQute.maven.bnd.MavenBndRepository
is a bnd plugin that represent the local and a remote Maven repository. The locations of both repositories can be configured. The local repository is always used as a cache for the remote repository.
For a detailed configuration of the Maven Bnd Repository Plugin, please look at the documentation page.
+ +If the Maven Bnd Repository is asked to put a file, it will look up the -maven-release
instruction using merged properties. The property is looked up from the bnd file that built the artifact. However, it should in general be possible to define this header in the workspace using macros like ${project}
to specify relative paths.
The archive
option provides a way to add additional files/archives to release. A Maven release always has a pom and then a number of files/archives that are separated by a classifier. The default classifier is generally the jar file. Special classifiers are reserved for the sources and the javadoc.
The archive
option takes the following parameters:
path
: The path to the file that will be placed in the release directory. If the path is surrounded by curly braces, it will be pre-processed.classifier
: The classifier of the file. This is the maven classifier used.For example:
+ + -maven-release \
+ archive;\
+ path=files/feature.json;
+ classifier=feature
+
If the instruction contains the sign attribute and release build is detected the repository tries to apply gnupg via a command process to create .asc
files for all deployed artifacts. This requires a Version of gnupg installed on your build system. By default it uses the gpg
command. If the passphrase
is configured, it will hand it over to the command as standard input. The command will be constructed as follows: gpg --batch --passphrase-fd 0 --output <filetosign>.asc --detach-sign --armor <filetosign>
. Some newer gnupg versions will ignore the passphrase via standard input for the first try and ask again with password screen. This will crash the process. Have a look here to teach gnupg otherwise. The command can be exchanged or amended with additional options by defining a property named gpg
in your workspace (e.g. build.bnd
or somewhere in the ext directory).
Example config could look like:
+ +# use the env macro to avoid to set the passphrase somehwere in your project
+-maven-release: pom,sign;passphrase=${env;GNUPG_PASSPHRASE}
+gpg: gpg --homedir /mnt/n/tmp/gpg/.gnupg --pinentry-mode loopback
+
The -pom instruction can be used to generate a pom in the bundle. The -maven-scope
instruction can be used to specify the default dependency scope to use when Bnd generates maven dependency information for a -buildpath entry that will be used to create a <dependency>
in the generated pom.
Also see the -maven-dependencies instruction for information on how to manualy configure the maven dependency information in a generated pom.
+ +-maven-scope
defaults to the value compile
which is the default dependency scope for Maven.
Change the default dependency scope to provided
.
-maven-scope: provided
+
-metatypeannotations-options: version;minimum=1.2.0
+
Analogous to -dsannotations-options
, this will also restrict the use of OSGi Metatype annotations to minimum 1.2.0 version. The version number denotes that the users can use any version equal to or higher than 1.2.0, provided that the users have the Metatype annotations included on the build path.
The -metatypeannotations
instruction tells bnd which bundle classes, if any, to search for Metatype Service annotations. bnd will then process those classes into Metatype XML resources.
The value of this instruction is a comma delimited list of fully qualified class names.
+ +The default value of this instruction is *
, which means that by default bnd will process all bundle classes looking for Metatype annotations.
The behavior of Metatype annotation processing can be further configured using the -metatypeannotations-options instruction.
+ + + +Create a name section (second part of manifest) with optional property expansion and addition of custom attributes.
+ +The key of the -namesection
instruction is an ant style glob. And there are two target groups for matching:
/
or is an exact match for a resource path/
or is an exact match for a package pathThe goal of named sections is to provide attributes over a specific subset of resources and paths in the jar file. Attributes are specified using the same syntax used elsewhere (such as package attributes). Attributes can contain properties and macros for expansion and replacement.
+ +Each attribute is processed by bnd and the matching value is passed using the @
property.
Resources are targeted by using a glob pattern not ending with /
.
For example, the following instruction sets the content type attribute for png
files:
-namesection: com/foo/*.png; Content-Type=image/png
+
This produces a result like the following:
+Name: org/foo/icon_12x12.png
+Content-Type: image/png
+
+Name: org/foo/icon_48x48.png
+Content-Type: image/png
+
Packages are targeted by using a glob pattern that ends with /
.
For example, to produce a Java Package Version Information section use an instruction like this one:
+-namesection: jakarta/annotation/*/;\
+ Specification-Title=Jakarta Annotations;\
+ Specification-Version=${annotation.spec.version};\
+ Specification-Vendor=Eclipse Foundation;\
+ Implementation-Title=jakarta.annotation;\
+ Implementation-Version=${annotation.spec.version}.${annotation.revision};\
+ Implementation-Vendor=Apache Software Foundation
+
This produces a result like the following:
+Name: jakarta/annotation/
+Implementation-Title: jakarta.annotation
+Implementation-Vendor: Apache Software Foundation
+Implementation-Version: 2.0.0-M1
+Specification-Title: Jakarta Annotations
+Specification-Vendor: Eclipse Foundation
+Specification-Version: 2.0
+
@Override
+protected void setTypeSpecificPlugins(Set<Object> list) {
+ try {
+ super.setTypeSpecificPlugins(list);
+ list.add(this);
+ list.add(maven);
+ list.add(settings);
+
+ if (!isTrue(getProperty(NOBUILDINCACHE))) {
+ list.add(new CachedFileRepo());
+ }
+
+ resourceRepositoryImpl = new ResourceRepositoryImpl();
+ resourceRepositoryImpl.setCache(IO.getFile(getProperty(CACHEDIR, "~/.bnd/caches/shas")));
+ resourceRepositoryImpl.setExecutor(getExecutor());
+ resourceRepositoryImpl.setIndexFile(getFile(buildDir, "repo.json"));
+ resourceRepositoryImpl.setURLConnector(new MultiURLConnectionHandler(this));
+ customize(resourceRepositoryImpl, null);
+ list.add(resourceRepositoryImpl);
+
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+}
+
Bnd will skip building the project with this instruction.
+ +For example:
+ +-nobundles: true
+
Normally Bnd will examine the method bodies of classes looking for the instruction sequence:
+ +ldc(_w) "some.Class"
+invokestatic "java/lang/Class" "forName(Ljava/lang/String;)Ljava/lang/Class;"
+
which results from calls to Class.forName(String)
passing a String constant for the class name. Bnd will use the String constant as a class reference for the purposes of calculating package references for generating the Import-Package manifest header.
The -noclassforname
instruction can be used to tell Bnd to not search the method bodies for this instruction sequence.
For example:
+ +-noclassforname: true
+
public void cleanupVersion(Packages packages, String defaultVersion) {
+ if (defaultVersion != null) {
+ Matcher m = Verifier.VERSION.matcher(defaultVersion);
+ if (m.matches()) {
+ // Strip qualifier from default package version
+ defaultVersion = Version.parseVersion(defaultVersion).getWithoutQualifier().toString();
+ }
+ }
+ for (Map.Entry<PackageRef,Attrs> entry : packages.entrySet()) {
+ Attrs attributes = entry.getValue();
+ String v = attributes.get(Constants.VERSION_ATTRIBUTE);
+ if (v == null && defaultVersion != null) {
+ if (!isTrue(getProperty(Constants.NODEFAULTVERSION))) {
+ v = defaultVersion;
+ if (isPedantic())
+ warning("Used bundle version %s for exported package %s", v, entry.getKey());
+ } else {
+ if (isPedantic())
+ warning("No export version for exported package %s", entry.getKey());
+ }
+ }
+ if (v != null)
+ attributes.put(Constants.VERSION_ATTRIBUTE, cleanupVersion(v));
+ }
+}
+
//
+ // We want to add the minimum EE as a requirement
+ // based on the class version
+ //
+
+ if (!isTrue(getProperty(NOEE)) //
+ && !ees.isEmpty() // no use otherwise
+ && since(About._2_3) // we want people to not have to
+ // automatically add it
+ && !requirements.containsKey(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE) // and
+ // it
+ // should
+ // not
+ // be
+ // there
+ // already
+ ) {
+
+ JAVA highest = ees.last();
+ Attrs attrs = new Attrs();
+
+ String filter = doEEProfiles(highest);
+
+ attrs.put(Constants.FILTER_DIRECTIVE, filter);
+
+ //
+ // Java 1.8 introduced profiles.
+ // If -eeprofile= auto | (<profile>="...")+ is set then
+ // we add a
+
+ requirements.add(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE, attrs);
+ }
+
boolean noExtraHeaders = "true".equalsIgnoreCase(getProperty(NOEXTRAHEADERS));
+
+ if (!noExtraHeaders) {
+ main.putValue(CREATED_BY, System.getProperty("java.version") + " (" + System.getProperty("java.vendor")
+ + ")");
+ main.putValue(TOOL, "Bnd-" + getBndVersion());
+ main.putValue(BND_LASTMODIFIED, "" + System.currentTimeMillis());
+ }
+
Prior to OSGi Core R7, it was invalid for the Import-Package header to include java.*
packages. So Bnd would never include them in the generated Import-Package header. In OSGi Core R7, or later, it is now permitted to include java.*
packages in the Import-Package header. This allows the OSGi framework validate the execution environment can supply all the java packages required by a bundle. This can avoid a NoClassDefFoundError
during execution of the bundle due to a missing java.*
package required by the bundle.
Bnd will now generate the Import-Package header including referenced java.*
packages when either the bundle imports the org.osgi.framework
package from OSGi Core R7, or later, or when the bundle includes class files targeting Java 11, or later.
The -noimportjava
instruction can be used to tell Bnd not to include referenced java.*
packages in the generated Import-Package header.
For example:
+ +-noimportjava: true
+
You can use the Import-Package
instruction to control which referenced java.*
packages should be imported.
For example:
+ +Import-Package: java.util.*, !java.*, *
+
will only import java.util.*
packages and no other java.*
packages.
private void checkForTesting(Project project, Properties properties) throws Exception {
+
+ //
+ // Only run junit when we have a test src directory
+ //
+
+ boolean junit = project.getTestSrc().isDirectory() && !Processor.isTrue(project.getProperty(Constants.NOJUNIT));
+ boolean junitOsgi = project.getProperties().getProperty(Constants.TESTCASES) != null
+ && !Processor.isTrue(project.getProperty(Constants.NOJUNITOSGI));
+
+ if (junit)
+ properties.setProperty("project.junit", "true");
+ if (junitOsgi)
+ properties.setProperty("project.osgi.junit", "true");
+}
+
private void checkForTesting(Project project, Properties properties) throws Exception {
+
+ //
+ // Only run junit when we have a test src directory
+ //
+
+ boolean junit = project.getTestSrc().isDirectory() && !Processor.isTrue(project.getProperty(Constants.NOJUNIT));
+ boolean junitOsgi = project.getProperties().getProperty(Constants.TESTCASES) != null
+ && !Processor.isTrue(project.getProperty(Constants.NOJUNITOSGI));
+
+ if (junit)
+ properties.setProperty("project.junit", "true");
+ if (junitOsgi)
+ properties.setProperty("project.osgi.junit", "true");
+}
+
public boolean isNoBundle() {
+ return isTrue(getProperty(RESOURCEONLY)) || isTrue(getProperty(NOMANIFEST));
+}
+
+
+public Jar build() throws Exception {
+ trace("build");
+ init();
+ if (isTrue(getProperty(NOBUNDLES)))
+ return null;
+
+ if (getProperty(CONDUIT) != null)
+ error("Specified " + CONDUIT + " but calls build() instead of builds() (might be a programmer error");
+
+ Jar dot = new Jar("dot");
+ try {
+ long modified = Long.parseLong(getProperty("base.modified"));
+ dot.updateModified(modified, "Base modified");
+ }
+ catch (Exception e) {
+ // Ignore
+ }
+ setJar(dot);
+
+ doExpand(dot);
+ doIncludeResources(dot);
+ doWab(dot);
+
+ // Check if we override the calculation of the
+ // manifest. We still need to calculated it because
+ // we need to have analyzed the classpath.
+
+ Manifest manifest = calcManifest();
+
+ String mf = getProperty(MANIFEST);
+ if (mf != null) {
+ File mff = getFile(mf);
+ if (mff.isFile()) {
+ try {
+ InputStream in = new FileInputStream(mff);
+ manifest = new Manifest(in);
+ in.close();
+ }
+ catch (Exception e) {
+ error(MANIFEST + " while reading manifest file", e);
+ }
+ } else {
+ error(MANIFEST + ", no such file " + mf);
+ }
+ }
+
+ if (getProperty(NOMANIFEST) == null) {
+ dot.setManifest(manifest);
+ String manifestName = getProperty(MANIFEST_NAME);
+ if (manifestName != null)
+ dot.setManifestName(manifestName);
+ } else {
+ dot.setDoNotTouchManifest();
+ }
+
+ // This must happen after we analyzed so
+ // we know what it is on the classpath
+ addSources(dot);
+
+ if (getProperty(POM) != null)
+ dot.putResource("pom.xml", new PomResource(dot.getManifest()));
+
+ if (!isNoBundle())
+ doVerify(dot);
+
+ if (dot.getResources().isEmpty())
+ warning("The JAR is empty: The instructions for the JAR named %s did not cause any content to be included, this is likely wrong",
+ getBsn());
+
+ dot.updateModified(lastModified(), "Last Modified Processor");
+ dot.setName(getBsn());
+
+ doDigests(dot);
+
+ sign(dot);
+ doSaveManifest(dot);
+
+ doDiff(dot); // check if need to diff this bundle
+ doBaseline(dot); // check for a baseline
+
+ String expand = getProperty("-expand");
+ if ( expand != null) {
+ File out = getFile(expand);
+ out.mkdirs();
+ dot.expand(out);
+ }
+ return dot;
+}
+
Gradle supports --parallel
to run build tasks in parallel when possible. This can be a great speed improvement for a build. But sometimes, certain tasks cannot be run in parallel due to certain resource contention.
The -noparallel
Bnd instruction can be used to state that any tasks assigned to a category must not be run in parallel with any other task assigned to the same category. The category names are open ended. Any task names specified must be the names of actual Gradle tasks in the project. Multiple categories and multiple task names per category can be specified.
For example:
+ +-noparallel: launchpad;task="test", port80;task="testOSGi"
+
Do not calculate the uses directive on package exports or on capabilities.
+ +For example:
+ +-nouses: true
+
TODO how does this relate to -outputmask?
+ +/** + * Calculate the output file for the given target. The strategy is: + * + * <pre> + * parameter given if not null and not directory + * if directory, this will be the output directory + * based on bsn-version.jar + * name of the source file if exists + * Untitled-[n] + * </pre> + * + * @param output + * may be null, otherwise a file path relative to base + */ + public File getOutputFile(String output) {
+ + if (output == null)
+ output = get(Constants.OUTPUT);
+
+ File outputDir;
+
+ if (output != null) {
+ File outputFile = getFile(output);
+ if (outputFile.isDirectory())
+ outputDir = outputFile;
+ else
+ return outputFile;
+ } else
+ outputDir = getBase();
+
+ Entry<String,Attrs> name = getBundleSymbolicName();
+ if (name != null) {
+ String bsn = name.getKey();
+ String version = getBundleVersion();
+ Version v = Version.parseVersion(version);
+ String outputName = bsn + "-" + v.getWithoutQualifier() + Constants.DEFAULT_JAR_EXTENSION;
+ return new File(outputDir, outputName);
+ }
+
+ File source = getJar().getSource();
+ if (source != null) {
+ String outputName = source.getName();
+ return new File(outputDir, outputName);
+ }
+
+ error("Cannot establish an output name from %s, nor bsn, nor source file name, using Untitled", output);
+ int n = 0;
+ File f = getFile(outputDir, "Untitled");
+ while (f.isFile()) {
+ f = getFile(outputDir, "Untitled-" + n++);
+ }
+ return f;
+}
+
/**
+ * Calculate the file for a JAR. The default name is bsn.jar, but this can
+ * be overridden with an
+ *
+ * @param jar
+ * @return
+ * @throws Exception
+ */
+public File getOutputFile(String bsn, String version) throws Exception {
+ if (version == null)
+ version = "0";
+ Processor scoped = new Processor(this);
+ try {
+ scoped.setProperty("@bsn", bsn);
+ scoped.setProperty("@version", version.toString());
+ String path = scoped.getProperty(OUTPUTMASK, bsn + ".jar");
+ return IO.getFile(getTarget(), path);
+ }
+ finally {
+ scoped.close();
+ }
+}
+
if (opts.jpm())
+ project.setProperty(Constants.PACKAGE, "jpm");
+
protected void begin() {
+ if (isTrue(getProperty(PEDANTIC)))
+ setPedantic(true);
+}
+
A plugin is a parameterized piece of code that runs inside bndlib. The -plugin
instruction defines a plugin by specifying its class name and a given set of parameters; a specific class can be instantiated multiple times.
The -plugin
instruction actually aggregates all properties that start with -plugin*
. This makes it possible to set plugins in different places, for example include files or with the bndlib workspace extensions. The following sets the Git plugin:
-plugin.git = aQute.bnd.plugin.git.GitPlugin
+
Plugins are created at startup using a special plugin class loader. This class loader is pre-loaded with any URLs set in the -pluginpath
instructions. The plugin definition can, however, also add additional URLs to this classloader with the ‘path:’ directive.
If the plugin implements the Plugin
interface, it is given the parameters specified in the -plugin
instruction. It is then registered in the plugin registry and made available to the rest of the system.
All plugins are (unfortunately) loaded in a single class loader.
+ +-plugin* ::= plugin-def ( ',' plugin-def )*
+plugin-def ::= qname ( ';' ( attribute | directive ) )*
+
The qname
must identify a class, if it is an interface then this will load proxies to any external plugins. This class will be loaded with the -pluginpath
and/or the path:
directive.
Any attributes are passed to the plugin if it implements the aQute.bnd.servce.Plugin
interface. Consult the actual plugin for the possible attributes.
The following directives are architected.
+ +path:
– This directive specifies a comma separated list of file paths, the list must be enclosed in quotes when it contains a comma. Each of these files must be a directory or a JAR file and is added to the plugin class loader in the given sequence.command:
– If this directive is specified errors on initializing this plugin are only reported if this command is an instruction in the current properties. The purpose of this is to allow plugins to be built in the cnf
directory; since the plugin does not exist during its first compilation errors would be reported. Since this project does not use the command itself, it can safely ignore this error.name
– Specifies the name of an external plugin. This name is a glob and can this be wildcarded. If not specified the name is *
, which will load all external plugins for that type.If the specified qname
identifies an interface then the current repositories are searched for external plugins that implement this interface. Any found implementations are turned into a proxy that will lazily load the implementation class. The attributes in the clause will be given as properties when the plugin implements Closeable. External plugins that implement Closeable will be closed as normal plugins.
The following example installs an embedded FileRepo and will load all exporters from this repository.
+ +-plugin.repo.main:\
+ aQute.lib.deployer.FileRepo; \
+ name='Main'; \
+ location=${build}/repo/main, \
+ aQute.bnd.service.export.Exporter;name=*
+
Problem adding path <path> to loader for plugin <key>. Exception: (<e>)
– An unexpected exceptio occurred while adding a new path to the plugin class loader.Cannot load the plugin <class-name>
– The give plugin cannot be loaded for an unknown reason.Failed to load plugin <class-name>;<attrs>, error: <e>
– An exception occurred trying to add a new plugin.While setting properties <properties> on plugin <plugin>, <e>
– An exception was thrown by the plugin when receiving its properties.Plugin path: <path>, specified url <url> and a sha1 but the file does not match the sha
– The -pluginpath.*
instruction specified a download URL and a SHA digest; the file was downloaded but the content did not match the given SHA digest.Plugin path: <path>, specified url <url> and a sha1 '<sha>' but this is not a hexadecimal
– The SHA is not in hexadecimal form.No such file <path> from <plugin> and no 'url' attribute on the path so it can be downloaded
– The given path in pluginpath*
was not found and there was no url
attribute specified to download it from.-pluginpath
– Specifies JARs that populat the plugin class loader-extensions
– An alternative mechanism that can load plugins from repositoriesPlugins not embedded in bndlib must load their classes from JARs or directories. Though these JARs can be specified on the -plugin
instruction, it is also possible to specify them separate. The -pluginpath
is a merged property so it is possible to specify clauses in multiple places, these will all be merged together.
/**
+ * Add the @link {@link Constants#PLUGINPATH} entries (which are file names)
+ * to the class loader. If this file does not exist, and there is a
+ * {@link Constants#PLUGINPATH_URL_ATTR} attribute then we download it first
+ * from that url. You can then also specify a
+ * {@link Constants#PLUGINPATH_SHA1_ATTR} attribute to verify the file.
+ *
+ * @see PLUGINPATH
+ * @param pluginPath
+ * the clauses for the plugin path
+ * @param loader
+ * The class loader to extend
+ */
+private void loadPluginPath(Set<Object> instances, String pluginPath, CL loader) {
+ Parameters pluginpath = new Parameters(pluginPath);
+
+ nextClause: for (Entry<String,Attrs> entry : pluginpath.entrySet()) {
+
+ File f = getFile(entry.getKey()).getAbsoluteFile();
+ if (!f.isFile()) {
+
+ //
+ // File does not exist! Check if we need to download
+ //
+
+ String url = entry.getValue().get(PLUGINPATH_URL_ATTR);
+ if (url != null) {
+ try {
+
+ trace("downloading %s to %s", url, f.getAbsoluteFile());
+ URL u = new URL(url);
+ URLConnection connection = u.openConnection();
+
+ //
+ // Allow the URLCOnnectionHandlers to interact with the
+ // connection so they can sign it or decorate it with
+ // a password etc.
+ //
+ for (Object plugin : instances) {
+ if (plugin instanceof URLConnectionHandler) {
+ URLConnectionHandler handler = (URLConnectionHandler) plugin;
+ if (handler.matches(u))
+ handler.handle(connection);
+ }
+ }
+
+ //
+ // Copy the url to the file
+ //
+ f.getParentFile().mkdirs();
+ IO.copy(connection.getInputStream(), f);
+
+ //
+ // If there is a sha specified, we verify the download
+ // of the
+ // the file.
+ //
+ String digest = entry.getValue().get(PLUGINPATH_SHA1_ATTR);
+ if (digest != null) {
+ if (Hex.isHex(digest.trim())) {
+ byte[] sha1 = Hex.toByteArray(digest);
+ byte[] filesha1 = SHA1.digest(f).digest();
+ if (!Arrays.equals(sha1, filesha1)) {
+ error("Plugin path: %s, specified url %s and a sha1 but the file does not match the sha",
+ entry.getKey(), url);
+ }
+ } else {
+ error("Plugin path: %s, specified url %s and a sha1 '%s' but this is not a hexadecimal",
+ entry.getKey(), url, digest);
+ }
+ }
+ }
+ catch (Exception e) {
+ error("Failed to download plugin %s from %s, error %s", entry.getKey(), url, e);
+ continue nextClause;
+ }
+ } else {
+ error("No such file %s from %s and no 'url' attribute on the path so it can be downloaded",
+ entry.getKey(), this);
+ continue nextClause;
+ }
+ }
+ trace("Adding %s to loader for plugins", f);
+ try {
+ loader.add(f.toURI().toURL());
+ }
+ catch (MalformedURLException e) {
+ // Cannot happen since every file has a correct url
+ }
+ }
+}
+
The -pom
instruction can generate a pom derived from the manifest and store it in the
+bundle. The groupId can be specified by the groupid
key which defaults to the value of
+the -groupid
instruction. If neither the groupid
key or the -groupid
instruction
+are specified, the groupId will be derived from the Bundle Symbolic Name by using
+everything until the last ‘.’ (bsn prefix) and the artifactId will be everything from the
+last ‘.’ to the end (bsn suffix).
The following properties are supported:
+ +Key | +Default | +Description | +
groupid |
+ -groupid |
+ The groupId to use. Will default to bsn prefix if no groupid is set. | +
artifactid |
+ bundle symbolic name | +The artifactId to use. Will default to bsn suffix if no groupid is set. | +
version |
+ bundle version | +The version to use. | +
where |
+ META-INF/maven/<groupid>/<artifactid>/pom.xml |
+ The location of the pom.xml file. Will default to pom.xml if no groupid is set. |
+
The -pom
instruction can use any macro but the ${@bsn}
and ${@version}
macros
+refer to the current JAR being built.
The -pom
instruction will also attempt to convert the following headers to their POM counterpart:
Bundle-Description
Bundle-DocUrl
Bundle-Vendor
– If the value ends with a HTTP or HTTPS url then this URL is used as the organization URL and the name with be the part without the URL. Otherwise the whole value is used as the value for the organization name.Bundle-License
Bundle-Developers
– This is an unofficial header. The key must be the email. It consist of the following parameters:Parameters | +Default | +Description | +
email |
+ + | Email address (mandatory) | +
id |
+ A developer id (defaults to email) | +|
name |
+ + | Name of the developer | +
organization |
+ + | Name of the organization | +
organizationUrl |
+ + | URL of the organization | +
roles |
+ + | Roles of the developer (comma separated) | +
timezone |
+ + | Three letter time zone | +
Bundle-Developers: \
+ Peter.Kriens@aQute.biz; \
+ name="Peter Kriens"; \
+ organization=aQute; \
+ roles="programmer,gopher"
+
Bundle-SCM
– This is an unofficial header. The key must be the It consists of the following parameters:Parameters | +Default | +Description | +
connection |
+ + | Read only connection | +
developerConnection |
+ + | Developer connection | +
url |
+ + | The URL for a web front end to your SCM system. | +
Bundle-SCM: \
+ url=https://github.com/bndtools, \
+ connection=scm:git:https://github.com/bndtools/bnd, \
+ developerConnection=scm:git:git@github.com/bndtools/bnd
+
The following example bnd file:
+ +Bundle-SymbolicName: com.example.foo
+Bundle-Version: 1.2.3.qualifier
+-pom: true
+
Generates the following pom in pom.xml
:
<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi=""
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.example</groupId>
+ <artifactId>foo</artifactId>
+ <version>1.2.3.qualifier</version>
+ <name>com.example.foo</name>
+</project>
+
You can override the different parts of the Maven coordinates:
+ +Bundle-SymbolicName: com.example.foo
+Bundle-Version: 1.2.3.qualifier
+Bundle-Developers: Peter.Kriens@aQute.biz; \
+ name="Peter Kriens"; \
+ organization=aQute; \
+ roles="programmer,gopher"
+Bundle-SCM: url=https://github.com/bndtools, \
+ connection=scm:git:https://github.com/bndtools/bnd, \
+ developerConnection=scm:git:git@github.com/bndtools/bnd
+-pom: groupid=com.example, \
+ where=META-INF/maven/pom.xml, \
+ version=${versionmask;==;${Bundle-Version}}
+
Generates the following pom in META-INF/maven/pom.xml
:
<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi=""
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.example</groupId>
+ <artifactId>com.example.foo</artifactId>
+ <version>1.2</version>
+ <name>com.example.foo</name>
+ <scm>
+ <url>https://github.com/bndtools</url>
+ <connection>scm:git:https://github.com/bndtools/bnd</connection>
+ <developerConnection>scm:git:git@github.com/bndtools/bnd</developerConnection>
+ </scm>
+ <developers>
+ <developer>
+ <id>Peter.Kriens@aQute.biz</id>
+ <name>Peter Kriens</name>
+ <organization>aQute</organization>
+ <roles>
+ <role>programmer</role>
+ <role>gopher</role>
+ </roles>
+ <email>Peter.Kriens@aQute.biz</email>
+ </developer>
+ </developers>
+</project>
+
An typical use case for the -prepare
instruction is the generation of CSS files from a less
or sccs
specification. The prepare instruction will execute a number of commands in the shell before it starts the build process.
The commands used in the shell must work on the platform. Since the shell is so different in a Windows environment this function is not guaranteed to work.
+ +The following example compiles typescript code in the typescript
directory. The output goes to web/repository.js
. To install the tsc
command, see npm
.
-prepare: \
+ web/foo.js <= typescript/*.ts ; \
+ command:=tsc -p typescript --out $@
+
-prepare ::= makespec ( ',' makespec )
+makespec ::= dependency ( ';' parameter )+
+dependency ::= PATH ( '<=' FILESPEC )?
+parameter ::= command | report | name | env
+command ::= 'command:=' <shell> # mandatory
+report ::= 'report:=' PATTERN
+name ::= 'name:=' STRING
+env ::= key '=' value
+
A makespec
defines a dependency in the file system. It must start with a file path, this is the output file of the command. It can be followed with a dependency
. A dependency
is a FILESPEC
. The FILESPEC
defines the set of input files to the command. If specified, then the command is only executed when the target file is older than any of the dependencies and the project bnd files.
It is possible to specify a name
for the command, this name is then used for any error reporting.
The command
parameter specifies the shell command. It must be a valid shell command. Any $@
and $<
macros are replaced by the target file path and the set of files in the dependency.
The shell is supposed to support multiple commands (separate with ‘;’) and pipes (‘ | +’). The working directory the command will run in is the project directory. | +
Any attributes (i.e. not directives) are added as an environment variable for the command.
+ +If the executed command provides diagnostic output with file names, line numbers, etc. then it is possible in some cases to report these to the IDE so the error/warning appears on the right place. This requires the specification of a regular expression. This regular expression must specify named groups. A named group is like (?<name>.*)
. The expression must find error messages from the console output or the error output.
The following names can be used in the regular expression:
+ +file relative file name
+line zero based line number
+line-1 one based line number
+type `error` | `warning`
+message the message
+
For example, the report pattern for the Microsoft TypeScript compiler tsc:
+ +"(^|\n)(?<file>[^(]+)\\((?<line>[0-9]+),(?<column>[0-9]+)\\):(?<message>[^\n]*)"
+
It is possible to set the platforms command search path with the -PATH
macro. This macro must contain a comma separated list of PATH, specifying places where to look. If this -PATH
macro contains ${@}
then this will be replaced with the platforms current PATH. By not specifying the ${@}
it is possible to limit the available commands.
During the -includeresource processing it is possible to pre-process the files that are copied into the JAR by enclosing the clause in curly braces ({}
). Since this can create havoc when applied to text files bnd will attempt to skip binary files. To skip binary files, bnd uses a pre-process matchers list. This list is a standard selector. The default is:
!*.(jpg|jpeg|jif|jfif|jp2|jpx|j2k|j2c|fpx|png|gif|swf|doc|pdf|tiff|tif|raw|bmp|ppm|pgm|pbm|pnm|pfm|webp|zip|jar|gz|tar|tgz|exe|com|bin|mp[0-9]|mpeg|mov|):i, *
+
When bnd copies a file from a source to a directory it will match that name against this list. If it is one of the extensions listed, then it will not preprocess that file.
+ +The default can be overridden with the -preprocessmatchers
instruction.
-preprocessmatchers: !OSGI-INF/*,*
+
/**
+ * Allow any local initialization by subclasses before we build.
+ */
+public void init() throws Exception {
+ begin();
+ doRequireBnd();
+
+ // Check if we have sensible setup
+
+ if (getClasspath().size() == 0
+ && (getProperty(EXPORT_PACKAGE) != null || getProperty(EXPORT_PACKAGE) != null || getProperty(PRIVATE_PACKAGE) != null || getProperty(PRIVATEPACKAGE) != null))
+ warning("Classpath is empty. " + Constants.PRIVATE_PACKAGE + " (-privatepackage) and " + EXPORT_PACKAGE + " can only expand from the classpath when there is one");
+
+}
+
+
+ /**
+ * Check if the given resource is in scope of this bundle. That is, it
+ * checks if the Include-Resource includes this resource or if it is a class
+ * file it is on the class path and the Export-Package or Private-Package
+ * include this resource.
+ *
+ * @param f
+ * @return
+ */
+public boolean isInScope(Collection<File> resources) throws Exception {
+ Parameters clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
+ clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
+ clauses.putAll(parseHeader(getProperty(Constants.PRIVATEPACKAGE)));
+ if (isTrue(getProperty(Constants.UNDERTEST))) {
+ clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES, "test;presence:=optional")));
+ }
+
+ Collection<String> ir = getIncludedResourcePrefixes();
+
+ Instructions instructions = new Instructions(clauses);
+
+ for (File r : resources) {
+ String cpEntry = getClasspathEntrySuffix(r);
+
+ if (cpEntry != null) {
+
+ if (cpEntry.equals("")) // Meaning we actually have a CPE
+ return true;
+
+ String pack = Descriptors.getPackage(cpEntry);
+ Instruction i = matches(instructions, pack, null, r.getName());
+ if (i != null)
+ return !i.isNegated();
+ }
+
+ // Check if this resource starts with one of the I-C header
+ // paths.
+ String path = r.getAbsolutePath();
+ for (String p : ir) {
+ if (path.startsWith(p))
+ return true;
+ }
+ }
+ return false;
+}
+
public Jar pack(String profile) throws Exception {
+ Collection< ? extends Builder> subBuilders = getSubBuilders();
+
+ if (subBuilders.size() != 1) {
+ error("Project has multiple bnd files, please select one of the bnd files");
+ return null;
+ }
+
+ Builder b = subBuilders.iterator().next();
+
+ ignore.remove(BUNDLE_SYMBOLICNAME);
+ ignore.remove(BUNDLE_VERSION);
+ ignore.add(SERVICE_COMPONENT);
+
+ ProjectLauncher launcher = getProjectLauncher();
+ launcher.getRunProperties().put("profile", profile); // TODO remove
+ launcher.getRunProperties().put(PROFILE, profile);
+ Jar jar = launcher.executable();
+ Manifest m = jar.getManifest();
+ Attributes main = m.getMainAttributes();
+ for (String key : getPropertyKeys(true)) {
+ if (Character.isUpperCase(key.charAt(0)) && !ignore.contains(key)) {
+ main.putValue(key, getProperty(key));
+ }
+ }
+
+ if (main.getValue(BUNDLE_SYMBOLICNAME) == null)
+ main.putValue(BUNDLE_SYMBOLICNAME, b.getBsn());
+
+ if (main.getValue(BUNDLE_SYMBOLICNAME) == null)
+ main.putValue(BUNDLE_SYMBOLICNAME, getName());
+
+ if (main.getValue(BUNDLE_VERSION) == null) {
+ main.putValue(BUNDLE_VERSION, Version.LOWEST.toString());
+ warning("No version set, uses 0.0.0");
+ }
+
+ jar.setManifest(m);
+ jar.calcChecksums(new String[] {
+ "SHA1", "MD5"
+ });
+ return jar;
+}
+
+
+ // Prevent recursion, but try to get a profiled variable
+ if (key != null && !key.startsWith("[") && !key.equals(Constants.PROFILE)) {
+ if (profile == null)
+ profile = domain.get(Constants.PROFILE);
+ if (profile != null) {
+ String replace = getMacro("[" + profile + "]" + key, link);
+ if (replace != null)
+ return replace;
+ }
+ }
+ return null;
+
The -provider-policy
instruction defines the semantic versioning policy to be used when a type is a provider. A provider is in general a type that is implemented by classes that are responsible for the contract implied by the package. In contrast, a consumer is the party that just uses the contract defined in the package. For example, when you implement Event Admin, the org.osgi.service.event package is your responsibility so the types you need to implement like EventAdmin
are provider types. (These types are annotated with a @ProviderType
annotation.) A casual user of the Event Admin service will be a consumer, the EventHandler
type is therefore annotated with a@ConsumerType
.
The purpose of this distinction is semantic versioning. It turns out that the relation between a consumer and a provider is not symmetric. A provider is tightly bound to a contract while a consumer is expected to have backward compatibility. Virtually any change to the contract requires the provider to adapt while a consumer is in almost all cases protect against changes.
+ +This asymmetry has a consequence for the semantic versioning. In the OSGi, the semantics are defined that a micro change does not affect the provider nor the consumer. A minor change affects the provider, and a major change affects both. Therefore, a bundle that implements a provider type must import a range from major.minor.micro
… major.minor+1.0
. A bundle that implements a consumer type must import major.minor.micro
… major+1.0.0
.
In theory, bnd could have hard coded these policies but there are always cases where the policy is just not right. The -provider-policy
specifies the macro to use for calculating the version range. The default definition is:
-consumer-policy ${range;[==,+)}
+-provider-policy ${range;[==,=+)}
+
The range macro works very much like the version macro. It uses a template to define a change the range/version.
+ +The provider and consumer policy are global and this is not very convenient if you want to make an exception just for a specific bundle. For example, a bundle coming from Gavin King’s Ceylon. For this reason, you can also specify a policy on an import:
+ +Import-Package com.gavinking.*;version="${range;[--,++)}", *
+
The counterpart of the -provider-policy
is of course the -consumer-policy.
You can specify zero or more repository names to use when releasing a project.
+ +The -releaserepo
instruction aggregates the -releaserepo
property and all properties that start with -releaserepo.
. This makes it possible to set release repository names in different places.
If the -releaserepo
instruction is set to the empty value, then releasing a project does nothing. If the -releaserepo
instruction is unset, then releasing a project will release it to the first writable repository.
Launchpad is a library that enables testing in local JUnit settings. Launchpad needs access to the enclosing +bnd workspace. However, this workspace runs in another process then the test code. Launchpad will therefore +attempt to a workspace remote server.
+ +For security reasons, this remote workspace server is not enabled by default. It requires:
+ +-remoteworkspace true
+
Remote Workspace servers can be nested. That is, you can run Eclipse and then Gradle on the same workspace. +Launchpad will use the latest initialized remote workspace.
+ +If you enable the remote workspace, its socket’s port will be registered in the /cnf/cache/remotews
directory.
The remote workspace server can only be accessed from the local machine on 127.0.0.1 to prevent outside +attacks.
+ + // Remove all the headers mentioned in -removeheaders
+ Instructions instructions = new Instructions(getProperty(REMOVEHEADERS));
+ Collection<Object> result = instructions.select(main.keySet(), false);
+ main.keySet().removeAll(result);
+
+
+ @Override
+public Jar executable() throws Exception {
+
+ // TODO use constants in the future
+ Parameters packageHeader = OSGiHeader.parseHeader(project.getProperty("-package"));
+ boolean useShas = packageHeader.containsKey("jpm");
+ project.trace("Useshas %s %s", useShas, packageHeader);
+
+ Jar jar = new Jar(project.getName());
+
+ Builder b = new Builder();
+ project.addClose(b);
+
+ if (!project.getIncludeResource().isEmpty()) {
+ b.setIncludeResource(project.getIncludeResource().toString());
+ b.setProperty(Constants.RESOURCEONLY, "true");
+ b.build();
+ if ( b.isOk()) {
+ jar.addAll(b.getJar());
+ }
+ project.getInfo(b);
+ }
+
+ List<String> runpath = getRunpath();
+
+ Set<String> runpathShas = new LinkedHashSet<String>();
+ Set<String> runbundleShas = new LinkedHashSet<String>();
+ List<String> classpath = new ArrayList<String>();
+
+ for (String path : runpath) {
+ project.trace("embedding runpath %s", path);
+ File file = new File(path);
+ if (file.isFile()) {
+ if (useShas) {
+ String sha = SHA1.digest(file).asHex();
+ runpathShas.add(sha + ";name=\"" + file.getName() + "\"");
+ } else {
+ String newPath = "jar/" + file.getName();
+ jar.putResource(newPath, new FileResource(file));
+ classpath.add(newPath);
+ }
+ }
+ }
+
+ // Copy the bundles to the JAR
+
+ List<String> runbundles = (List<String>) getRunBundles();
+ List<String> actualPaths = new ArrayList<String>();
+
+ for (String path : runbundles) {
+ project.trace("embedding run bundles %s", path);
+ File file = new File(path);
+ if (!file.isFile())
+ project.error("Invalid entry in -runbundles %s", file);
+ else {
+ if (useShas) {
+ String sha = SHA1.digest(file).asHex();
+ runbundleShas.add(sha + ";name=\"" + file.getName() + "\"");
+ actualPaths.add("${JPMREPO}/" + sha);
+ } else {
+ String newPath = "jar/" + file.getName();
+ jar.putResource(newPath, new FileResource(file));
+ actualPaths.add(newPath);
+ }
+ }
+ }
+
+ LauncherConstants lc = getConstants(actualPaths, true);
+ lc.embedded = !useShas;
+ lc.storageDir = null; // cannot use local info
+
+ final Properties p = lc.getProperties();
+
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ p.store(bout, "");
+ jar.putResource(LauncherConstants.DEFAULT_LAUNCHER_PROPERTIES, new EmbeddedResource(bout.toByteArray(), 0L));
+
+ Manifest m = new Manifest();
+ Attributes main = m.getMainAttributes();
+
+ for (Entry<Object,Object> e : project.getFlattenedProperties().entrySet()) {
+ String key = (String) e.getKey();
+ if (key.length() > 0 && Character.isUpperCase(key.charAt(0)))
+ main.putValue(key, (String) e.getValue());
+ }
+
+ Instructions instructions = new Instructions(project.getProperty(Constants.REMOVEHEADERS));
+ Collection<Object> result = instructions.select(main.keySet(), false);
+ main.keySet().removeAll(result);
+
+ if (useShas) {
+ project.trace("Use JPM launcher");
+ m.getMainAttributes().putValue("Main-Class", JPM_LAUNCHER_FQN);
+ m.getMainAttributes().putValue("JPM-Classpath", Processor.join(runpathShas));
+ m.getMainAttributes().putValue("JPM-Runbundles", Processor.join(runbundleShas));
+ URLResource jpmLauncher = new URLResource(this.getClass().getResource("/" + JPM_LAUNCHER));
+ jar.putResource(JPM_LAUNCHER, jpmLauncher);
+ } else {
+ project.trace("Use Embedded launcher");
+ m.getMainAttributes().putValue("Main-Class", EMBEDDED_LAUNCHER_FQN);
+ m.getMainAttributes().putValue(EmbeddedLauncher.EMBEDDED_RUNPATH, Processor.join(classpath));
+ URLResource embeddedLauncher = new URLResource(this.getClass().getResource("/" + EMBEDDED_LAUNCHER));
+ jar.putResource(EMBEDDED_LAUNCHER, embeddedLauncher);
+ }
+ if ( project.getProperty(Constants.DIGESTS) != null)
+ jar.setDigestAlgorithms(project.getProperty(Constants.DIGESTS).trim().split("\\s*,\\s*"));
+ else
+ jar.setDigestAlgorithms(new String[]{"SHA-1", "MD-5"});
+ jar.setManifest(m);
+ return jar;
+}
+
The purpose of the -reportconfig
instruction is to configure the content of the reports exported with the -exportreport
instruction.
When a report is generated, a set of plugins is used to extract a specific piece of data from the source (for example, the information contain in metatypes from a bundle source). Those plugins are generally designed to not require a configuration and are silently ignored if they do not find any data, thus, this instruction should rarely be used.
+ +Additional plugins can be declared and configured with the -plugin.* instruction and will be available for all your reports, however the -reportconfig
instruction gives more control on the plugins that should be used when generating a specific report. This instruction diverges from the -plugins.*
instruction as you can declare named configuration, for example -reportconfig.api-bundle:...
will have the name api-bundle
that you can then use for a specific report -exportreport:file.json;configName=api-bundle
. In addition, this instruction allows to declare plugins with a short name instead of the canonical name of the plugin class (importFile
instead of biz.aQute.bnd.reporter.plugins.entries.any.ImportResourcePlugin
).
See -exportreport instruction documentation.
+ +-reportconfig.xxx ::= plugin-def ( ',' plugin-def ) *
+plugin-def ::= plugin | 'clearDefaults'
+plugin ::= ( qname | plugin-name ) ( ';' parameters ) *
+plugin-name ::= extended
+
where xxx is the name of the configuration.
+ +One use case is when you want a specific resource from a bundle in the report but where there is no plugins to extract it. For this you can use the importJarFile
plugin which need the path to the resource inside the bundle and will import it into the report.
For example, if you need blueprint data:
+ +bnb.bnd
+ +-reportconfig.blueprint: importJarFile;path=OSGI-INF/blueprint/component.xml
+-exportreport: metadata.json;configName=blueprint
+
When you set a -reportconfig.xxx
instruction, a list of default plugins will be added to the specified list. If you do not want the default plugins you can use the special property clearDefaults
:
bnb.bnd
+ +-reportconfig.blueprint: \
+ importJarFile;path=OSGI-INF/blueprint/component.xml, \
+ clearDefaults
+
This section describes the available plugins in Bnd, additional plugins may be provided by a specific build tool.
+ +All plugins have the entryName
property which can be set to override the name under which data will be aggregated into the reports (this corresponds to a tag name when serialised in XML
):
-reportconfig.bundle: metatypes;entryName=serviceConfigs
+
This plugin allows the user to define an arbitrary entry.
+ +anyEntry
key
: the name under which the value will be available in the report.value
: the value of the entry.-reportconfig.api-bundle:anyEntry;key=bundleType;value=api
+
This plugin allows to add a local or remote file to the report. The type of the file can be: properties
, manifest
, XML
and JSON
.
importFile
url
: URL to the file.type
: The type of the file. (optional)-reportconfig.bundle:importFile;url=http://<...>/myFile.json
+
This plugin allows to add a file contains in a bundle to the report. The type of the file can be: properties
, manifest
, XML
and JSON
.
importJarFile
path
: Path from the root of the Jar.type
: The type of the file. (optional)-reportconfig.bundle:
+
Add some common data to the report. If data are extracted from the workspace, the following properties will be read in the build.bnd file: ws-name
, ws-description
, ws-version
, ws-icons
, ws-docURL
, ws-updateLocation
, ws-licenses
, ws-developers
, ws-scm
, ws-copyright
, ws-vendor
, ws-contactAddress
; otherwise, the corresponding headers will be read. If data are extracted from a project, the above properties will be read in the bnd.bnd file but with a p-
prefix, such as p-contactAddress
.
commonInfo
Add the OSGI headers to the report.
+ +manifest
Add a list of the declarative services.
+ +components
Add a list of the metatypes.
+ +metatypes
Add the file name or the folder name in which the source is backed up.
+ +fileName
Add a list of bundle data (for example, the bundles built by a project).
+ +bundles
useConfig
: The configuration name used when generating the data. (optional)excludes
: A list of bundle symbolic names to exclude. (optional)-reportconfig.project:bundles;useConfig=api-bundle;excludes="com.domain.product.provider"
+
Add a list of code snippet extracted from source files in a project. By default, source files containing code snippets must be located in a <root test source dir>/examples
folder and its sub directories (eg; src/test/java/examples/**
). The actual supported file types are: java
.
Code snippets can either be a single sample code with a title and a description or have multiple steps.
+ +codeSnippets
path
: The directory path from which code snippets will be looked up. (optional)You can either export a full type or a method. For the snippet to be extracted you must add a comment block to the type or the method. The comment must contain the ${snippet }
tag with the following optional properties:
title
(String): A title for the code snippet.description
(String): A short description of the code snippet.includeDeclaration
(Boolean): If true, the type declaration (or the method declaration) will be include, otherwise only its content. Default is true.includeImports
(Boolean): If true, the imports declration will be include. Default is true.groupName
(String): A user defined group name. If set, it will turn the actual snippet into a snippet with multiple steps, only the title and the description considered then and the snippet children must reference its groupName
in their parentGroup
property.parentGroup
(String): The parent group name. If set, it will make the actual snippet a step in the referenced snippet group.id
(String): A user defined id of the snippet. By default, the Id is the type name or the method name, optionally appended by a counting starting from 1 if not unique (eg; MyType, MyType1, etc).Here is an example illustrating a code snippet with steps:
+ +/**
+ * ${snippet title=How to Print Something in Java, description="In this example we will show how to
+ * print a string to the console.", groupName=print}
+ */
+public class PrintExample {
+
+ /**
+ * ${snippet title=Print No New Line, description="Here, we print without a new line at the end.",
+ * parentGroup=print, includeDeclaration=false}
+ */
+ public void printNoNewLine() {
+ System.out.print("Hello");
+ }
+
+ /**
+ * ${snippet title=Print With New Line, description="Here, we print with a new line at the end.",
+ * parentGroup=print, includeDeclaration=false}
+ */
+ public void printWithNewLine() {
+ System.out.println("Hello");
+ }
+}
+
Which will result in the following json
object:
{
+ "id": "PrintExample",
+ "title":"How to Print Something in Java",
+ "description":"In this example we will show how to print a string to the console.",
+ "steps":[
+ {
+ "id": "printNoNewLine",
+ "title":"Print No New Line",
+ "description":"Here, we print without a new line at the end.",
+ "programmingLangague":"java",
+ "codeSnippet":"System.out.print(\"Hello\");"
+ },
+ {
+ "id": "printWithNewLine",
+ "title":"Print With New Line",
+ "description":"Here, we print with a new line at the end.",
+ "programmingLangague":"java",
+ "codeSnippet":"System.out.println(\"Hello\");"
+ }
+ ]
+}
+
Add a list of project data (for example, the projects built by the workspace).
+ +projects
useConfig
: The configuration name used when generating the data. (optional)excludes
: A list of project names to exclude.-reportconfig.ws:projects;useConfig=bnd-proj;excludes="maven-index"
+
Add the maven coordinate of the bundle (extracted from the pom.properties file).
+ +mavenCoordinate
Add a list of Gogo commands.
+ +gogoCommands
private void reportNewer(long lastModified, Jar jar) {
+ if (isTrue(getProperty(Constants.REPORTNEWER))) {
+ StringBuilder sb = new StringBuilder();
+ String del = "Newer than " + new Date(lastModified);
+ for (Map.Entry<String,Resource> entry : jar.getResources().entrySet()) {
+ if (entry.getValue().lastModified() > lastModified) {
+ sb.append(del);
+ del = ", \n ";
+ sb.append(entry.getKey());
+ }
+ }
+ if (sb.length() > 0)
+ warning(sb.toString());
+ }
+}
+
To ensure the bundle can be built in a reproducible manner, the timestamp of the zip entries is set to the fixed time 1980-02-01T00:00:00Z
when the value of this instruction is true
.
+The value can also be set to either an ISO-8601 formatted time or the number of seconds since the epoch which is used as the timestamp for the zip entries.
+The Bnd-LastModified
header is also omitted from the manifest.
+The default value is false
.
For example:
+ +-reproducible: true
+-reproducible: 1641127394
+-reproducible: 2022-01-02T12:43:14Z
+
Each specified filter must evaluate to true for the running version of Bnd in the version
attribute. Since the values of the instruction are filter expressions, they need to quoted so the filter operators are not processed by Bnd.
This instruction can be useful when the workspace requires a feature of Bnd introduced in some version of Bnd. For example:
+ +-require-bnd: "(version>=4.3.0)"
+
Each requirement and capability has an effective
or is effective=resolve
. An effective of resolve
is always processed by the resolver.However, in (very) special cases it is necessary to provide more rules.
The -resolve.effective
syntax is as follows:
-resolve.effective ::= effective ( ',' effective )*
+effective ::= NAME (';skip:=' skip )
+skip ::= skip = '"' namespace ( ',' namespace ) * '"'
+
The simplest model is to just list the names, for example:
+ +-resolve.effective: resolve,active
+
In this case, the resolver will only look at requirements that are either resolve or active.
+ +Adding a meta
effective could then be:
-resolve.effective: resolve,active, meta
+
However, in very, very rare (usually error) cases it is necessary to exclude certain namespaces. This can be done by using the skip:
directive.
-resolve.effective: resolve,active, meta;skip:='osgi.extender,osgi.wiring.package'
+
This property has no meaning in the normal configuration. It is used by code that needs to +have the system resource in the wiring. Normally the wiring excludes the system resource. +However, sometimes it is necessary to see the full solution.
+ +The bnd workspace can use a resolver to calculate the content of the -runbundles
instruction based on a set of initial requirements. The bndtools GUI can manually resolve the initial requirements but through the -resolve
instruction it is possible to calculate the -runbundles
when the file is saved or just before the -runbundles
are used in the launch.
The values are:
+ +manual
– It is up to the user to resolve the initial requirementsauto
– Whenever the initial requirements are saved, the resolver will be used to set new -runbundles
beforelaunch
– Calculate the -runbundles
on demand. This ignores the value of the -runbundles
and runs the resolver. The results of the resolver are cached. This cache works by creating a checksum over all the properties of the project.batch
– When running in batch mode, the run bundles will be resolved. In all other modes this will only resolve when the -runbundles
are empty.cache
– Will use a cache file in the workspace cache. If that file is stale relative to the workspace or project or it does not exist, then the bnd(run) file will be resolved and the result is stored in the cache file.-resolve beforelaunch
+
The resolver normally finds a lost of capabilities that match a given requirement. This list has an order defined by the context. However, in certain occasions this order is not the desired order. The -resolve.preferences
allows you to override this context order. It is an ordered list of Bundle Symbolic Names. The list of capabilities will always be adjusted to have the bundles in the -resolver.preferences
always first when they are present.
For example:
+ +`-resolve.preferences` : \
+ com.example.bundle.most.priority, \
+ com.example.bundle.less.priority, \
+ com.example.whatever
+
Given that for a requirement the capabilties come from:
+ +com.example.some.bundle,
+om.example.bundle.less.priority,
+com.example.another.bundle,
+com.example.most.priority
+
Then the resulting order will be:
+ +com.example.most.priority
+om.example.bundle.less.priority,
+com.example.some.bundle,
+com.example.another.bundle,
+
Preferences should only be used when blacklisting is not a better solution.
+ +The resolver will request capabilities from the resolve context. In bnd, the resolve hook can be
+used to reject some of these capabilities. The -resolve.reject
instruction can control the default
+hook. Without this instruction, nothing is rejected and the resolver sees all capabilities. With this
+hook it is possible to reject
To directly reject a namespace foo
, the instruction is simply:
-resolve.reject foo
+
This will reject any capability in the namespace foo
. To reject a namespace foo
where a filter matches, the instruction is:
-resolve.reject foo;filter:='(foo=3)`
+
This will reject any capability in the namespace foo
that has an attribute foo
that has the value 3.
Sometimes it is necessary to reject a capability depending on its resource. For example, you want to reject
+capabilities that come from fragments. You can achieve this by prefixing the requirement with
+a @
(commercial at sign). For example, if you want to reject any capabilities that come from a resource that has identity type osgi.fragment
.
-resolve.reject @osgi.identity;filter:='(type=osgi.fragment)'
+
You can add multiple requirements in the specification. These requirements will be or’ed together. The following + specification:
+ +-resolve.reject @foo, baz
+
Will reject any capabiliy from a resource that has a foo
capability and it will remove all baz
capabilities.
Before bnd release 7, the resolver always rejected resources that were neither a bundle nor a fragment. This was +changed to allow more scenarios. The filtering was moved to the list of bundles where it is necessary that the resources +are all bundles.
+ +This should normally not make a difference during resolving. However, in some cases you want to have the old +situation. The following example restores the pre release 7 situation.
+ +-resolve.reject @osgi.identity;filter:='(!(|(type=osgi.bundle)(type=osgi.fragment)))'
+
When resolving, the -resolvedebug
instruction can be used to request debug information about the resolve is displayed to System.out
. The value 0
turns off displaying debug information. The values 1
, 2
, and 3
display progressively more debug information.
-resolvedebug: 1
+
public boolean isNoBundle() {
+ return isTrue(getProperty(RESOURCEONLY)) || isTrue(getProperty(NOMANIFEST));
+}
+
+/**
+ * @return
+ */
+boolean isResourceOnly() {
+ return isTrue(getProperty(RESOURCEONLY));
+}
+
The blacklist is a set of requirements. These requirements are used to get a set of resources from the repositories that match any of these requirements. This set is then removed from any result from the repositories, effectively making it impossible to use.
+ +For example:
+ +-runblacklist: \
+ osgi.identity;filter:='(osgi.identity=com.foo.bad.bundle)'
+
public boolean getRunBuilds() {
+ boolean result;
+ String runBuildsStr = getProperty(Constants.RUNBUILDS);
+ if (runBuildsStr == null)
+ result = !getPropertiesFile().getName().toLowerCase().endsWith(Constants.DEFAULT_BNDRUN_EXTENSION);
+ else
+ result = Boolean.parseBoolean(runBuildsStr);
+ return result;
+}
+
+protected void updateFromProject() throws Exception {
+ // pkr: could not use this because this is killing the runtests.
+ // project.refresh();
+ runbundles.clear();
+ Collection<Container> run = project.getRunbundles();
+
+ for (Container container : run) {
+ File file = container.getFile();
+ if (file != null && (file.isFile() || file.isDirectory())) {
+ runbundles.add(file.getAbsolutePath());
+ } else {
+ error("Bundle file \"%s\" does not exist, given error is %s", file, container.getError());
+ }
+ }
+
+ if (project.getRunBuilds()) {
+ File[] builds = project.build();
+ if (builds != null)
+ for (File file : builds)
+ runbundles.add(file.getAbsolutePath());
+ }
+
+ Collection<Container> runpath = project.getRunpath();
+ runsystempackages = new Parameters( project.mergeProperties(Constants.RUNSYSTEMPACKAGES));
+ runsystemcapabilities = project.mergeProperties(Constants.RUNSYSTEMCAPABILITIES);
+ framework = getRunframework(project.getProperty(Constants.RUNFRAMEWORK));
+
+ timeout = Processor.getDuration(project.getProperty(Constants.RUNTIMEOUT), 0);
+ trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
+
+ runpath.addAll(project.getRunFw());
+
+ for (Container c : runpath) {
+ addClasspath(c);
+ }
+
+ runvm.addAll(project.getRunVM());
+ runprogramargs.addAll(project.getRunProgramArgs());
+ runproperties = project.getRunProperties();
+
+ storageDir = project.getRunStorage();
+ if (storageDir == null) {
+ storageDir = new File(project.getTarget(), "fw");
+ }
+}
+
The runbundles instruction is used to specify which bundles should be installed when a framework is started. This is the primary mechanism to run applications directly from bnd/bndtools. A bundle listed in -runbundles can be either a workspace bundle (a bundle created by one of the workspace’s projects) or a bundle from one of the configured repositories. Note that all required bundles to run the application should be listed, transitive dependencies are not handles automatically so that there is full control over the runtime.
+ +This list can be maintained manually it is normally calculated by the resolver. That is, when a resolve is run then it will, without warning, override this list.
+ +For example:
+ +-runbundles: \
+ org.apache.felix.configadmin;version='[1.8.8,1.8.9)',\
+ org.apache.felix.http.jetty;version='[3.2.0,3.2.1)',\
+ org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\
+ ...
+ osgi.enroute.twitter.bootstrap.webresource;version='[3.3.5,3.3.6)',\
+ osgi.enroute.web.simple.provider;version='[2.1.0,2.1.1)'
+
Some launchers support startlevels and the -runbundles
instruction therefore has a startlevel
attribute. This attribute
+must be a positive integer larger than 0.
-runbundles: \
+ org.apache.felix.configadmin;version='[1.8.8,1.8.9)'; startlevel=100,\
+ org.apache.felix.http.jetty;version='[3.2.0,3.2.1)'; startlevel=110,\
+ ...
+
Since the common workflow is to use the resolver to calculate the set of run bundles, any start level settings +would be overridden after the next resolve. There are the following solutions.
+ +Use the -runstartlevel instruction to let the resolver calculate the start level ordering. In that case the
+resolver will add the startlevel
attribute.
Use the decoration facility. With the decoration facility you can augment the -runbundles
instruction by
+specifying the -runbundles+
property (or the -runbundles++
if you want to add literals). The keys are glob expressions
+and any attributes or directives will be set (or overridden) on the merged -runbundles
instruction.
-runbundles: \
+ org.apache.felix.configadmin;version='[1.8.8,1.8.9)',\
+ org.apache.felix.http.jetty;version='[3.2.0,3.2.1)',\
+ org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\
+
+-runbundles+: \
+ org.apache.felix.servlet-api;startlevel=100, \
+ *;startlevel=110
+
The -runee
instruction adds the capabilities of an execution environment to the system capabilities. Every Java edition has a set of standard packages and OSGi has also defined a number of execution environments that define which packages can be found. The -runee
allows these capabilities to be defined by using the name of the execution environment. Additionally, this instruction also adds an osgi.ee
requirement with the given name and version. You can use the following execution environment names:
OSGi/Minimum-1.0
+OSGi/Minimum-1.1
+OSGi/Minimum-1.2
+JRE-1.1
+J2SE-1.2
+J2SE-1.3
+J2SE-1.4
+J2SE-1.5
+JavaSE-1.6
+JavaSE-1.7
+JavaSE/compact1-1.8
+JavaSE/compact2-1.8
+JavaSE/compact3-1.8
+JavaSE-1.8
+JavaSE-9
+
An example:
+ +-runee: JavaSE-1.8
+
public Map<String,String> getRunEnv() {
+ String runenv = project.getProperty(Constants.RUNENV);
+ if ( runenv != null) {
+ return OSGiHeader.parseProperties(runenv);
+ }
+ return Collections.emptyMap();
+}
+
+public int launch() throws Exception {
+ prepare();
+ java = new Command();
+
+
+ //
+ // Handle the environment
+ //
+
+ Map<String,String> env = getRunEnv();
+ for ( Map.Entry<String,String> e:env.entrySet()) {
+ java.var(e.getKey(), e.getValue());
+ }
+
+ java.add(project.getProperty("java", "java"));
+ String javaagent = project.getProperty(Constants.JAVAAGENT);
+ if (Processor.isTrue(javaagent)) {
+ for (String agent : agents) {
+ java.add("-javaagent:" + agent);
+ }
+ }
+
+ String jdb = getRunJdb();
+ if (jdb != null) {
+ int port = 1044;
+ try {
+ port = Integer.parseInt(project.getProperty(Constants.RUNJDB));
+ }
+ catch (Exception e) {
+ // ok, value can also be ok, or on, or true
+ }
+ String suspend = port > 0 ? "y" : "n";
+
+ java.add("-Xrunjdwp:server=y,transport=dt_socket,address=" + Math.abs(port) + ",suspend=" + suspend);
+ }
+
+ java.add("-cp");
+ java.add(Processor.join(getClasspath(), File.pathSeparator));
+ java.addAll(getRunVM());
+ java.add(getMainTypeName());
+ java.addAll(getRunProgramArgs());
+ if (timeout != 0)
+ java.setTimeout(timeout + 1000, TimeUnit.MILLISECONDS);
+
+ File cwd = getCwd();
+ if (cwd != null)
+ java.setCwd(cwd);
+
+ project.trace("cmd line %s", java);
+ try {
+ int result = java.execute(System.in, System.err, System.err);
+ if (result == Integer.MIN_VALUE)
+ return TIMEDOUT;
+ reportResult(result);
+ return result;
+ }
+ finally {
+ cleanup();
+ listeners.clear();
+ }
+}
+
Note: confusing name due to history, -runfw specifies the framework
+ +/**
+ * Collect all the aspect from the project and set the local fields from
+ * them. Should be called
+ *
+ * @throws Exception
+ */
+protected void updateFromProject() throws Exception {
+ // pkr: could not use this because this is killing the runtests.
+ // project.refresh();
+ runbundles.clear();
+ Collection<Container> run = project.getRunbundles();
+
+ for (Container container : run) {
+ File file = container.getFile();
+ if (file != null && (file.isFile() || file.isDirectory())) {
+ runbundles.add(file.getAbsolutePath());
+ } else {
+ error("Bundle file \"%s\" does not exist, given error is %s", file, container.getError());
+ }
+ }
+
+ if (project.getRunBuilds()) {
+ File[] builds = project.build();
+ if (builds != null)
+ for (File file : builds)
+ runbundles.add(file.getAbsolutePath());
+ }
+
+ Collection<Container> runpath = project.getRunpath();
+ runsystempackages = new Parameters( project.mergeProperties(Constants.RUNSYSTEMPACKAGES));
+ runsystemcapabilities = project.mergeProperties(Constants.RUNSYSTEMCAPABILITIES);
+ framework = getRunframework(project.getProperty(Constants.RUNFRAMEWORK));
+
+ timeout = Processor.getDuration(project.getProperty(Constants.RUNTIMEOUT), 0);
+ trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
+
+ runpath.addAll(project.getRunFw());
+
+ for (Container c : runpath) {
+ addClasspath(c);
+ }
+
+ runvm.addAll(project.getRunVM());
+ runprogramargs.addAll(project.getRunProgramArgs());
+ runproperties = project.getRunProperties();
+
+ storageDir = project.getRunStorage();
+ if (storageDir == null) {
+ storageDir = new File(project.getTarget(), "fw");
+ }
+}
+
+ private int getRunframework(String property) {
+ if (Constants.RUNFRAMEWORK_NONE.equalsIgnoreCase(property))
+ return NONE;
+ else if (Constants.RUNFRAMEWORK_SERVICES.equalsIgnoreCase(property))
+ return SERVICES;
+
+ return SERVICES;
+}
+
+
+private Framework createFramework() throws Exception {
+ Properties p = new Properties();
+ p.putAll(properties);
+ File workingdir = null;
+ if (parms.storageDir != null)
+ workingdir = parms.storageDir;
+ else if (parms.keep && parms.name != null) {
+ workingdir = new File(bnd, parms.name);
+ }
+
+ if (workingdir == null) {
+ workingdir = File.createTempFile("osgi.", ".fw");
+ final File wd = workingdir;
+ Runtime.getRuntime().addShutdownHook(new Thread("launcher::delete temp working dir") {
+ public void run() {
+ deleteFiles(wd);
+ }
+ });
+ }
+
+ trace("using working dir: %s", workingdir);
+
+ if (!parms.keep && workingdir.exists()) {
+ trace("deleting working dir %s because not kept", workingdir);
+ delete(workingdir);
+ p.setProperty(Constants.FRAMEWORK_STORAGE_CLEAN, "true");
+ }
+
+ if (!workingdir.exists() && !workingdir.mkdirs()) {
+ throw new IOException("Could not create directory " + workingdir);
+ }
+ if (!workingdir.isDirectory())
+ throw new IllegalArgumentException("Cannot create a working dir: " + workingdir);
+
+ p.setProperty(Constants.FRAMEWORK_STORAGE, workingdir.getAbsolutePath());
+
+ if (parms.systemPackages != null) {
+ p.setProperty(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, parms.systemPackages);
+ trace("system packages used: %s", parms.systemPackages);
+ }
+
+ if (parms.systemCapabilities != null) {
+ p.setProperty(FRAMEWORK_SYSTEM_CAPABILITIES_EXTRA, parms.systemCapabilities);
+ trace("system capabilities used: %s", parms.systemCapabilities);
+ }
+
+ Framework systemBundle;
+
+ if (parms.services) {
+ trace("using META-INF/services");
+ // 3) framework = null, lookup in META-INF/services
+
+ ClassLoader loader = getClass().getClassLoader();
+
+ // 3) Lookup in META-INF/services
+ List<String> implementations = getMetaInfServices(loader, FrameworkFactory.class.getName());
+
+ if (implementations.size() == 0)
+ error("Found no fw implementation");
+ if (implementations.size() > 1)
+ error("Found more than one framework implementations: %s", implementations);
+
+ String implementation = implementations.get(0);
+
+ Class< ? > clazz = loader.loadClass(implementation);
+ FrameworkFactory factory = (FrameworkFactory) clazz.newInstance();
+ trace("Framework factory %s", factory);
+ @SuppressWarnings("unchecked")
+ Map<String,String> configuration = (Map) p;
+ systemBundle = factory.newFramework(configuration);
+ trace("framework instance %s", systemBundle);
+ } else {
+ trace("using embedded mini framework because we were told not to use META-INF/services");
+ // we have to use our own dummy framework
+ systemBundle = new MiniFramework(p);
+ }
+ systemBundle.init();
+
+ try {
+ systemBundle.getBundleContext().addFrameworkListener(new FrameworkListener() {
+
+ public void frameworkEvent(FrameworkEvent event) {
+ switch (event.getType()) {
+ case FrameworkEvent.ERROR :
+ case FrameworkEvent.WAIT_TIMEDOUT :
+ trace("Refresh will end due to error or timeout %s", event.toString());
+
+ case FrameworkEvent.PACKAGES_REFRESHED :
+ inrefresh = false;
+ trace("refresh ended");
+ break;
+ }
+ }
+ });
+ }
+ catch (Exception e) {
+ trace("could not register a framework listener: %s", e);
+ }
+ trace("inited system bundle %s", systemBundle);
+ return systemBundle;
+}
+
Any bundle can stop the framework. After stopping, the launcher receives a notification and normally
+exit the process with System.exit
. In some cases, usually testing, it is necessary to do a restart
+in the local VM.
If -runframeworkrestart
is set to true
, the launcher will not do a hard exit after the framework is stopped,
+but will restart the framework after doing the normal clean up.
The launcher keeps a system property launcher.framework.restart.count
that is set to the iteration, it is initially zero.
The -runfw
instruction sets the framework to use. This framework will be added to the -runpath
. Any exported packages or capabilities listed in the manifest of the framework are automatically added to the system capabilities.
For example:
+ +-runfw: org.eclipse.osgi; version=3.10
+
Note – Do not use runframework
, this instruction is deprecated and had very different intent and syntax.
This instruction launches the VM with the
+ +-agentlib:jdwp=transport=dt_socket,server=y,address=<address>,suspend=y
+
command line argument.
+ +The socket transport address can include a host name (or IP address) and a port. For example:
+ +-runjdb: localhost:10001
+
The socket transport address can be just a port number. For example:
+ +-runjdb: 10001
+
Note: Starting with Java 9, using just a port number means that the launched VM will only listen on localhost
for the connection. If you want to remote debug, you will need to specify a host name (or IP address), or an asterisk (*
) to accept from any host. For example:
-runjdb: *:10001
+
If the socket transport address starts with a minus sign (-
), then the launched VM is not suspended: suspend=n
. The minus sign is removed from the socket transport address before it is used for the address
option.
If the specified socket transport address is not a port number or a host:port value, then the address 1044
is used.
On non-Windows machines, the more efficient reference:
URLs are used by default - use -runnoreferences: true
to override this. On Windows this instruction is ignored - reference:
URLs are never used due to Windows being more obstinate about open files.
private LauncherConstants getConstants(Collection<String> runbundles, boolean exported) throws Exception, FileNotFoundException,
+ IOException {
+ project.trace("preparing the aQute launcher plugin");
+
+ LauncherConstants lc = new LauncherConstants();
+ lc.noreferences = Processor.isTrue(project.getProperty(Constants.RUNNOREFERENCES));
+ lc.runProperties = getRunProperties();
+ lc.storageDir = getStorageDir();
+ lc.keep = isKeep();
+ lc.runbundles.addAll(runbundles);
+ lc.trace = getTrace();
+
+
+ Bundle install(File f) throws Exception {
+ BundleContext context = systemBundle.getBundleContext();
+ try {
+ String reference;
+ if (isWindows() || parms.noreferences) {
+ trace("no reference: url %s", parms.noreferences);
+ reference = f.toURI().toURL().toExternalForm();
+ } else
+ reference = "reference:" + f.toURI().toURL().toExternalForm();
+
+ Bundle b = context.installBundle(reference);
+ if (b.getLastModified() < f.lastModified()) {
+ b.update();
+ }
+ return b;
+ }
+ catch (BundleException e) {
+ trace("failed reference, will try to install %s with input stream", f.getAbsolutePath());
+ String reference = f.toURI().toURL().toExternalForm();
+ InputStream in = new FileInputStream(f);
+ try {
+ return context.installBundle(reference, in);
+ }
+ finally {
+ in.close();
+ }
+ }
+}
+
An OSGi application will have a set of bundles and an environment created by the framework and any additional JARs on the class path. The -runpath
instruction sets these additional bundles. These JARs can actually export packages and provide capabilities that the launcher will automatically add to the system capabilities. The resolver will do the same. Any packages exported by bundles or provided capabilities on the -runpath
are automatically added to the system capabilities.
For example:
+ +-runpath: \
+ com.foo.bar;version=1, \
+ file.jar; version=file
+
public Collection<String> getRunProgramArgs() {
+ Parameters hdr = getParameters(RUNPROGRAMARGS);
+ return hdr.keySet();
+}
+
+
+/**
+ * Collect all the aspect from the project and set the local fields from
+ * them. Should be called
+ *
+ * @throws Exception
+ */
+protected void updateFromProject() throws Exception {
+ // pkr: could not use this because this is killing the runtests.
+ // project.refresh();
+ runbundles.clear();
+ Collection<Container> run = project.getRunbundles();
+
+ for (Container container : run) {
+ File file = container.getFile();
+ if (file != null && (file.isFile() || file.isDirectory())) {
+ runbundles.add(file.getAbsolutePath());
+ } else {
+ error("Bundle file \"%s\" does not exist, given error is %s", file, container.getError());
+ }
+ }
+
+ if (project.getRunBuilds()) {
+ File[] builds = project.build();
+ if (builds != null)
+ for (File file : builds)
+ runbundles.add(file.getAbsolutePath());
+ }
+
+ Collection<Container> runpath = project.getRunpath();
+ runsystempackages = new Parameters( project.mergeProperties(Constants.RUNSYSTEMPACKAGES));
+ runsystemcapabilities = project.mergeProperties(Constants.RUNSYSTEMCAPABILITIES);
+ framework = getRunframework(project.getProperty(Constants.RUNFRAMEWORK));
+
+ timeout = Processor.getDuration(project.getProperty(Constants.RUNTIMEOUT), 0);
+ trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
+
+ runpath.addAll(project.getRunFw());
+
+ for (Container c : runpath) {
+ addClasspath(c);
+ }
+
+ runvm.addAll(project.getRunVM());
+ runprogramargs.addAll(project.getRunProgramArgs());
+ runproperties = project.getRunProperties();
+
+ storageDir = project.getRunStorage();
+ if (storageDir == null) {
+ storageDir = new File(project.getTarget(), "fw");
+ }
+}
+
+
+ public int launch() throws Exception {
+ prepare();
+ java = new Command();
+
+
+ //
+ // Handle the environment
+ //
+
+ Map<String,String> env = getRunEnv();
+ for ( Map.Entry<String,String> e:env.entrySet()) {
+ java.var(e.getKey(), e.getValue());
+ }
+
+ java.add(project.getProperty("java", "java"));
+ String javaagent = project.getProperty(Constants.JAVAAGENT);
+ if (Processor.isTrue(javaagent)) {
+ for (String agent : agents) {
+ java.add("-javaagent:" + agent);
+ }
+ }
+
+ String jdb = getRunJdb();
+ if (jdb != null) {
+ int port = 1044;
+ try {
+ port = Integer.parseInt(project.getProperty(Constants.RUNJDB));
+ }
+ catch (Exception e) {
+ // ok, value can also be ok, or on, or true
+ }
+ String suspend = port > 0 ? "y" : "n";
+
+ java.add("-Xrunjdwp:server=y,transport=dt_socket,address=" + Math.abs(port) + ",suspend=" + suspend);
+ }
+
+ java.add("-cp");
+ java.add(Processor.join(getClasspath(), File.pathSeparator));
+ java.addAll(getRunVM());
+ java.add(getMainTypeName());
+ java.addAll(getRunProgramArgs());
+ if (timeout != 0)
+ java.setTimeout(timeout + 1000, TimeUnit.MILLISECONDS);
+
+ File cwd = getCwd();
+ if (cwd != null)
+ java.setCwd(cwd);
+
+ project.trace("cmd line %s", java);
+ try {
+ int result = java.execute(System.in, System.err, System.err);
+ if (result == Integer.MIN_VALUE)
+ return TIMEDOUT;
+ reportResult(result);
+ return result;
+ }
+ finally {
+ cleanup();
+ listeners.clear();
+ }
+}
+
+
+ public int start(ClassLoader parent) throws Exception {
+
+ prepare();
+
+ //
+ // Intermediate class loader to not load osgi framework packages
+ // from bnd's loader. Unfortunately, bnd uses some osgi classes
+ // itself that would unnecessarily constrain the framework.
+ //
+
+ ClassLoader fcl = new ClassLoader(parent) {
+ protected Class< ? > loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ if ( IGNORE.matcher(name).matches())
+ throw new ClassNotFoundException();
+
+ return super.loadClass(name, resolve);
+ }
+ };
+
+ //
+ // Load the class that would have gone to the class path
+ // i.e. the framework etc.
+ //
+
+ List<URL> cp = new ArrayList<URL>();
+ for (String path : getClasspath()) {
+ cp.add(new File(path).toURI().toURL());
+ }
+ URLClassLoader cl = new URLClassLoader(cp.toArray(new URL[cp.size()]), fcl);
+
+
+ String[] args = getRunProgramArgs().toArray(new String[0]);
+
+ Class< ? > main = cl.loadClass(getMainTypeName());
+ return invoke(main, args);
+}
+
public Map<String,String> getRunProperties() {
+ return OSGiHeader.parseProperties(getProperty(RUNPROPERTIES));
+}
+
+public Launcher(Properties properties, final File propertiesFile) throws Exception {
+ this.properties = properties;
+
+ // Allow the system to override any properties with -Dkey=value
+
+ for (Object key : properties.keySet()) {
+ String s = (String) key;
+ String v = System.getProperty(s);
+ if (v != null)
+ properties.put(key, v);
+ }
+
+
+ System.getProperties().putAll(properties);
+
+
+ this.parms = new LauncherConstants(properties);
+ out = System.err;
+
This instruction works only with the -distro
instruction. It provides the capabilities that are not listed in the distro file but that are still provided by the target system.
-runprovidedcapabilities: \
+ some.namespace; \
+ some.namespace=foo
+
The -runrepos
instruction is used to restrict or order the available repositories. A bndrun
file can be based on a workspace or can be standalone. In the workspace case the, the repositories are defined in build.bnd
or in a *.bnd
file in the cnf/ext
directory as bnd plugins. In the standalone case the repositories are either OSGi XML repositories listed in the -standalone
instruction or they are also defined as plugins but now in the bndrun
file.
In both cases there is an ordered list of repositories. In the -standalone
it is easy to change this order or exclude repositories. However, in the workspace case this is harder because the set of repositories is shared with many other projects. The -runrepos
can then be used to exclude and reorder the list repositories. It simply lists the names of the repositories in the desired order. Each repository has its own name.
Note The name of a repository is not well defined. It is either the name of the repository or the toString()
result. In the later case the name is sometimes a bit messy.
For example:
+ +-runrepos: Maven Central, Main, Distro
+
The -runrequires
instruction specifies root requirements for a resolve operation. These requirements are input into the resolver
+which generates a list of bundles (the -runbundles
list) that can satisfy the requirements.
Each requirement must be in the format of an entry in the OSGi standard Require-Capability
header. See Section 3.3.6 (“Bundle Requirements”) of OSGi Core Release 6 specification.
A requirement can specify any arbitrary namespace, including but not limited to those listed in Chapter 8 (“Framework Namespaces Specification”) of OSGi Core Release 6 specification and Chapter 135 (“Common Namespaces Specification”) of OSGi Compendium Release 6.
+ +Bnd also supports alias namespaces – see below.
+ +-runrequires: \
+ osgi.identity; filter:='(osgi.identity=org.example.foo)',\
+ osgi.identity; filter:='(&(osgi.identity=org.example.bar)(version>=1.0)(!(version>=2.0)))'
+
This specifies a requirement for the resource identified as “org.example.foo” (with any version) AND the resource identified as “org.example.bar” with version in the range 1.0 inclusive to 2.0 exclusive.
+ +To ease manual entry of requirements bnd supports alias namespaces which translate to standard namespaces and filters.
+ +In all cases, attributes and directives that are not consumed by the alias are passed through to the generated requirement. For example if the bnd.identity
alias is used with a directive of resolution:=optional
then the generated osgi.identity
requirement shall also have the directive resolution:=optional
.
The following aliases are supported:
+ +bnd.identity
The bnd.identity
namepace alias takes the following attributes:
id
: the identity of the resourceversion
: a version range in conventional OSGi form, e.g. [1.0, 2.0)
.Example:
+ +-runrequires:\
+ bnd.identity; id=org.example.foo,\
+ bnd.identity; id=org.example.bar; version=1.0,\
+ bnd.identity; id=org.example.baz; version='[1.0,2.0)'
+
is translated to:
+ +-runrequires:\
+ osgi.identity; filter:='(osgi.identity=org.example.foo)',\
+ osgi.identity; filter:='(&(osgi.identity=org.example.bar)(version>=1.0))',\
+ osgi.identity; filter:='(&(osgi.identity=org.example.baz)(version>=1.0)(!(version>=2.0)))'
+
bnd.literal
The bnd.literal
alias can be used if you need to create a literal requirement that has a namespace clashing with one of the aliases. For example if you want to have a requirement with the literal namespace bnd.identity
, i.e. not processed as an alias.
Only one attribute is used:
+ +bnd.literal
: specifies the literal namespaceExample:
+ +-runrequires:\
+ bnd.literal; bnd.literal=bnd.identity; filter:='(bnd.identity=foo)',\
+ bnd.literal; bnd.literal=bnd.literal; filter:='(bnd.literal=bar)'
+
is translated to:
+ +-runrequires:\
+ bnd.identity; filter:='(bnd.identity=foo)',\
+ bnd.literal; filter:='(bnd.literal=bar)'
+
After a resolve the resolver calculates a number of resources that are mapped to bundles. This mapping can
+include ordering and assigned startlevels. The basic instruction that parameterizes this is -runstartlevel
.
If -runstartlevel
is not set the set of -runbundles will be sorted by name and version after which it is merged with the existing
+-runbundles
. Setting the -runstartlevel
makes it possible to let bnd assign startlevels based on different
+ordering strategies.
This instruction has the following syntax:
+ +-runstartlevel ::= runstartlevel ( ',' runstartlevel )
+runstartlevel :: order | begin | step
+order ::= 'order=' ORDER
+ORDER ::=
+ 'leastdependenciesfirst'
+ | 'leastdependencieslast'
+ | 'random'
+ | 'sortbynameversion'
+ | 'mergesortbynameversion'
+begin ::= 'begin=' NUMBER
+step ::= 'step=' NUMBER
+
The final -runstartlevel
flattens the properties so that the last of each of order
, begin
, or step
will be used.
The value of order
can take on the following values:
mergesortbynameversion
– Ordering by name (and version) and then merging was the original behavior. This is therefore the default.sortbynameversion
– For completeness, this option orders the bundles by name and version and then assigns a startlevel.random
– Use a random ordering. The ordering uses an algorithm that is based on the random number generator and should therefore
+be different on each run.leastdependenciesfirst
– Sort the resources topologically and place the resources with the least dependencies first.leastdependencieslast
– Sort the resources topologically and place the resources with the least dependencies last.The topological sorting algorithm is based on Tarjan. It can handle cyclic dependencies well. However, cyclic dependencies +make the ordering not perfect.
+ +After the resources have been resolved they are sorted according to the order
. If the begin
attribute is set and
+higher than 0, the resources will be assigned a startlevel that starts at the given value. By default, the step for each
+bundle is 10. (A lesson taught to us by BASIC) The step can be overridden by setting the step
value.
If the begin
value is not set, or it is set to a value < 1, then no startlevel attribute is added. However, the
+order of the -runbundles
will be in the specified order
. In most cases, this is then some kind of natural ordering
+since launchers start these
If you set the step
to 0 then all bundles will be assigned the same startlevel. However, the -runbundles
has the proper
+order.
-runstartlevel: \
+ order = leastdependenciesfirst, \
+ begin = 1000, \
+ step = 1
+
After resolving, this can generate:
+ +-runbundles: \
+ org.apache.felix.configadmin;version='[1.8.8,1.8.9)';startlevel=1000,\
+ org.apache.felix.http.jetty;version='[3.2.0,3.2.1);startlevel=1001',\
+ org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3);startlevel=1002',\
+ ...
+ osgi.enroute.twitter.bootstrap.webresource;version='[3.3.5,3.3.6);startlevel=1019',\
+ osgi.enroute.web.simple.provider;version='[2.1.0,2.1.1);startlevel=1020'
+
storageDir = project.getRunStorage();
+ if (storageDir == null) {
+ storageDir = new File(project.getTarget(), "fw");
+ }
+
+
+ private Framework createFramework() throws Exception {
+ Properties p = new Properties();
+ p.putAll(properties);
+ File workingdir = null;
+ if (parms.storageDir != null)
+ workingdir = parms.storageDir;
+ else if (parms.keep && parms.name != null) {
+ workingdir = new File(bnd, parms.name);
+ }
+
+ if (workingdir == null) {
+ workingdir = File.createTempFile("osgi.", ".fw");
+ final File wd = workingdir;
+ Runtime.getRuntime().addShutdownHook(new Thread("launcher::delete temp working dir") {
+ public void run() {
+ deleteFiles(wd);
+ }
+ });
+ }
+
+ trace("using working dir: %s", workingdir);
+
+ if (!parms.keep && workingdir.exists()) {
+ trace("deleting working dir %s because not kept", workingdir);
+ delete(workingdir);
+ p.setProperty(Constants.FRAMEWORK_STORAGE_CLEAN, "true");
+ }
+
+ if (!workingdir.exists() && !workingdir.mkdirs()) {
+ throw new IOException("Could not create directory " + workingdir);
+ }
+ if (!workingdir.isDirectory())
+ throw new IllegalArgumentException("Cannot create a working dir: " + workingdir);
+
+ p.setProperty(Constants.FRAMEWORK_STORAGE, workingdir.getAbsolutePath());
+
+ if (parms.systemPackages != null) {
+ p.setProperty(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, parms.systemPackages);
+ trace("system packages used: %s", parms.systemPackages);
+ }
+
+ if (parms.systemCapabilities != null) {
+ p.setProperty(FRAMEWORK_SYSTEM_CAPABILITIES_EXTRA, parms.systemCapabilities);
+ trace("system capabilities used: %s", parms.systemCapabilities);
+ }
+
+ Framework systemBundle;
+
+ if (parms.services) {
+ trace("using META-INF/services");
+ // 3) framework = null, lookup in META-INF/services
+
+ ClassLoader loader = getClass().getClassLoader();
+
+ // 3) Lookup in META-INF/services
+ List<String> implementations = getMetaInfServices(loader, FrameworkFactory.class.getName());
+
+ if (implementations.size() == 0)
+ error("Found no fw implementation");
+ if (implementations.size() > 1)
+ error("Found more than one framework implementations: %s", implementations);
+
+ String implementation = implementations.get(0);
+
+ Class< ? > clazz = loader.loadClass(implementation);
+ FrameworkFactory factory = (FrameworkFactory) clazz.newInstance();
+ trace("Framework factory %s", factory);
+ @SuppressWarnings("unchecked")
+ Map<String,String> configuration = (Map) p;
+ systemBundle = factory.newFramework(configuration);
+ trace("framework instance %s", systemBundle);
+ } else {
+ trace("using embedded mini framework because we were told not to use META-INF/services");
+ // we have to use our own dummy framework
+ systemBundle = new MiniFramework(p);
+ }
+ systemBundle.init();
+
+ try {
+ systemBundle.getBundleContext().addFrameworkListener(new FrameworkListener() {
+
+ public void frameworkEvent(FrameworkEvent event) {
+ switch (event.getType()) {
+ case FrameworkEvent.ERROR :
+ case FrameworkEvent.WAIT_TIMEDOUT :
+ trace("Refresh will end due to error or timeout %s", event.toString());
+
+ case FrameworkEvent.PACKAGES_REFRESHED :
+ inrefresh = false;
+ trace("refresh ended");
+ break;
+ }
+ }
+ });
+ }
+ catch (Exception e) {
+ trace("could not register a framework listener: %s", e);
+ }
+ trace("inited system bundle %s", systemBundle);
+ return systemBundle;
+}
+
Although resolver analyses the -runpath
(and thus -runfw
) for system capabilities, this is not always sufficient. For example, if the target system has special hardware then this might be described with a capability. Such an external capability must be explicitly given to the resolver. These extra capabilities maybe given with the -runsystemcapabilities
instruction.
For example:
+ +-runsystemcapabilities: \
+ some.namespace; \
+ some.namespace=foo
+
The resolver will analyse the -runpath
and -runfw
JARs for any exported packages and make these available as system packages to the bundles. However, in certain cases this automatic analysis does not suffice. In that case extra packages can be added. These packages must, of course, be available on the class path.
For example:
+ +-runsystempackages: \
+ com.sun.misc
+
public void prepare() throws Exception {
+ Pattern tests = Pattern.compile(project.getProperty(Constants.TESTSOURCES, "(.*).java"));
+
+ String testDirName = project.getProperty("testsrc", "test");
+ File testSrc = project.getFile(testDirName).getAbsoluteFile();
+ if (!testSrc.isDirectory()) {
+ project.trace("no test src directory");
+ return;
+ }
+
+ if (!traverse(fqns, testSrc, "", tests)) {
+ project.trace("no test files found in %s", testSrc);
+ return;
+ }
+
+ timeout = Processor.getDuration(project.getProperty(Constants.RUNTIMEOUT), 0); // trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
+ cp = new Classpath(project, "junit");
+ addClasspath(project.getTestpath());
+ addClasspath(project.getBuildpath());
+}
+
+
+public int launch() throws Exception {
+ java = new Command();
+ java.add(project.getProperty("java", "java"));
+
+ java.add("-cp");
+ java.add(cp.toString());
+ java.addAll(project.getRunVM());
+ java.add(getMainTypeName());
+ java.addAll(fqns);
+ if (timeout != 0)
+ java.setTimeout(timeout + 1000, TimeUnit.MILLISECONDS);
+
+ project.trace("cmd line %s", java);
+ try {
+ int result = java.execute(System.in, System.err, System.err);
+ if (result == Integer.MIN_VALUE)
+ return TIMEDOUT;
+ reportResult(result);
+ return result;
+ }
+ finally {
+ cleanup();
+ }
+
+}
+
+public static long getDuration(String tm, long dflt) {
+ if (tm == null)
+ return dflt;
+
+ tm = tm.toUpperCase();
+ TimeUnit unit = TimeUnit.MILLISECONDS;
+ Matcher m = Pattern
+ .compile("\\s*(\\d+)\\s*(NANOSECONDS|MICROSECONDS|MILLISECONDS|SECONDS|MINUTES|HOURS|DAYS)?").matcher(
+ tm);
+ if (m.matches()) {
+ long duration = Long.parseLong(tm);
+ String u = m.group(2);
+ if (u != null)
+ unit = TimeUnit.valueOf(u);
+ duration = TimeUnit.MILLISECONDS.convert(duration, unit);
+ return duration;
+ }
+ return dflt;
+}
+
+
+/**
+ * Collect all the aspect from the project and set the local fields from
+ * them. Should be called
+ *
+ * @throws Exception
+ */
+protected void updateFromProject() throws Exception {
+ // pkr: could not use this because this is killing the runtests.
+ // project.refresh();
+ runbundles.clear();
+ Collection<Container> run = project.getRunbundles();
+
+ for (Container container : run) {
+ File file = container.getFile();
+ if (file != null && (file.isFile() || file.isDirectory())) {
+ runbundles.add(file.getAbsolutePath());
+ } else {
+ error("Bundle file \"%s\" does not exist, given error is %s", file, container.getError());
+ }
+ }
+
+ if (project.getRunBuilds()) {
+ File[] builds = project.build();
+ if (builds != null)
+ for (File file : builds)
+ runbundles.add(file.getAbsolutePath());
+ }
+
+ Collection<Container> runpath = project.getRunpath();
+ runsystempackages = new Parameters( project.mergeProperties(Constants.RUNSYSTEMPACKAGES));
+ runsystemcapabilities = project.mergeProperties(Constants.RUNSYSTEMCAPABILITIES);
+ framework = getRunframework(project.getProperty(Constants.RUNFRAMEWORK));
+
+ timeout = Processor.getDuration(project.getProperty(Constants.RUNTIMEOUT), 0);
+ trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
+
+ runpath.addAll(project.getRunFw());
+
+ for (Container c : runpath) {
+ addClasspath(c);
+ }
+
+ runvm.addAll(project.getRunVM());
+ runprogramargs.addAll(project.getRunProgramArgs());
+ runproperties = project.getRunProperties();
+
+ storageDir = project.getRunStorage();
+ if (storageDir == null) {
+ storageDir = new File(project.getTarget(), "fw");
+ }
+}
+
+
+ public void deactivate() throws Exception {
+ if (active.getAndSet(false)) {
+ systemBundle.stop();
+ systemBundle.waitForStop(parms.timeout);
+
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ Thread[] threads = new Thread[group.activeCount() + 100];
+ group.enumerate(threads);
+ {
+ for (Thread t : threads) {
+ if (t != null && !t.isDaemon() && t.isAlive()) {
+ trace("alive thread " + t);
+ }
+ }
+ }
+ } else
+ errorAndExit("Huh? Already deactivated.");
+}
+
+
+ private void doTimeoutHandler() {
+ // Ensure we properly close in a separate thread so that
+ // we can leverage the main thread, which is required for macs
+ Thread wait = new Thread("FrameworkWaiter") {
+ @Override
+ public void run() {
+ try {
+ FrameworkEvent result = systemBundle.waitForStop(parms.timeout);
+ if (!active.get()) {
+ trace("ignoring timeout handler because framework is already no longer active, shutdown is orderly handled");
+ return;
+ }
+
+ trace("framework event " + result + " " + result.getType());
+ switch (result.getType()) {
+ case FrameworkEvent.STOPPED :
+ trace("framework event stopped");
+ System.exit(LauncherConstants.STOPPED);
+ break;
+
+ case FrameworkEvent.WAIT_TIMEDOUT :
+ trace("framework event timedout");
+ System.exit(LauncherConstants.TIMEDOUT);
+ break;
+
+ case FrameworkEvent.ERROR :
+ System.exit(ERROR);
+ break;
+
+ case FrameworkEvent.WARNING :
+ System.exit(WARNING);
+ break;
+
+ case FrameworkEvent.STOPPED_BOOTCLASSPATH_MODIFIED :
+ case FrameworkEvent.STOPPED_UPDATE :
+ trace("framework event update");
+ System.exit(UPDATE_NEEDED);
+ break;
+ }
+ }
+ catch (InterruptedException e) {
+ System.exit(CANCELED);
+ }
+ }
+ };
+ wait.start();
+}
+
public void run() throws Exception {
+ ProjectLauncher pl = getProjectLauncher();
+ pl.setTrace(isTrace() || isTrue(getProperty(RUNTRACE)));
+ pl.launch();
+}
+
+public void runLocal() throws Exception {
+ ProjectLauncher pl = getProjectLauncher();
+ pl.setTrace(isTrace() || isTrue(getProperty(RUNTRACE)));
+ pl.start(null);
+}
+
+
+ /**
+ * Collect all the aspect from the project and set the local fields from
+ * them. Should be called
+ *
+ * @throws Exception
+ */
+protected void updateFromProject() throws Exception {
+ // pkr: could not use this because this is killing the runtests.
+ // project.refresh();
+ runbundles.clear();
+ Collection<Container> run = project.getRunbundles();
+
+ for (Container container : run) {
+ File file = container.getFile();
+ if (file != null && (file.isFile() || file.isDirectory())) {
+ runbundles.add(file.getAbsolutePath());
+ } else {
+ error("Bundle file \"%s\" does not exist, given error is %s", file, container.getError());
+ }
+ }
+
+ if (project.getRunBuilds()) {
+ File[] builds = project.build();
+ if (builds != null)
+ for (File file : builds)
+ runbundles.add(file.getAbsolutePath());
+ }
+
+ Collection<Container> runpath = project.getRunpath();
+ runsystempackages = new Parameters( project.mergeProperties(Constants.RUNSYSTEMPACKAGES));
+ runsystemcapabilities = project.mergeProperties(Constants.RUNSYSTEMCAPABILITIES);
+ framework = getRunframework(project.getProperty(Constants.RUNFRAMEWORK));
+
+ timeout = Processor.getDuration(project.getProperty(Constants.RUNTIMEOUT), 0);
+ trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
+
+ runpath.addAll(project.getRunFw());
+
+ for (Container c : runpath) {
+ addClasspath(c);
+ }
+
+ runvm.addAll(project.getRunVM());
+ runprogramargs.addAll(project.getRunProgramArgs());
+ runproperties = project.getRunProperties();
+
+ storageDir = project.getRunStorage();
+ if (storageDir == null) {
+ storageDir = new File(project.getTarget(), "fw");
+ }
+}
+
+
+ @Override
+public boolean prepare() throws Exception {
+ if (!prepared) {
+ prepared = true;
+ super.prepare();
+ ProjectLauncher launcher = getProjectLauncher();
+ if (port > 0) {
+ launcher.getRunProperties().put(TESTER_PORT, "" + port);
+ if (host != null)
+ launcher.getRunProperties().put(TESTER_HOST, "" + host);
+
+ }
+ launcher.getRunProperties().put(TESTER_UNRESOLVED, project.getProperty(Constants.TESTUNRESOLVED, "true"));
+
+ launcher.getRunProperties().put(TESTER_DIR, getReportDir().getAbsolutePath());
+ launcher.getRunProperties().put(TESTER_CONTINUOUS, "" + getContinuous());
+ if (Processor.isTrue(project.getProperty(Constants.RUNTRACE)))
+ launcher.getRunProperties().put(TESTER_TRACE, "true");
+
+ try {
+ // use reflection to avoid NoSuchMethodError due to change in
+ // API
+ File cwd = (File) getClass().getMethod("getCwd").invoke(this);
+ if (cwd != null)
+ launcher.setCwd(cwd);
+ }
+ catch (NoSuchMethodException e) {
+ // ignore
+ }
+
+ Collection<String> testnames = getTests();
+ if (testnames.size() > 0) {
+ launcher.getRunProperties().put(TESTER_NAMES, Processor.join(testnames));
+ }
+ // This is only necessary because we might be picked
+ // as default and that implies we're not on the -testpath
+ launcher.addDefault(Constants.DEFAULT_TESTER_BSN);
+ launcher.prepare();
+ }
+ return true;
+}
+
+
+ @SuppressWarnings("deprecation")
+public int activate() throws Exception {
+ active.set(true);
+ Policy.setPolicy(new AllPolicy());
+
+ systemBundle = createFramework();
+ if (systemBundle == null)
+ return LauncherConstants.ERROR;
+
+ doTimeoutHandler();
+
+ doSecurity();
+
+ // Initialize this framework so it becomes STARTING
+ systemBundle.start();
+ trace("system bundle started ok");
+
+ BundleContext systemContext = systemBundle.getBundleContext();
+ ServiceReference<PackageAdmin> ref = systemContext.getServiceReference(PackageAdmin.class);
+ if (ref != null) {
+ padmin = systemContext.getService(ref);
+ } else
+ trace("could not get package admin");
+
+ systemContext.addServiceListener(this, "(&(|(objectclass=" + Runnable.class.getName() + ")(objectclass="
+ + Callable.class.getName() + "))(main.thread=true))");
+
+ // Start embedded activators
+ trace("start embedded activators");
+ if (parms.activators != null) {
+ ClassLoader loader = getClass().getClassLoader();
+ for (Object token : parms.activators) {
+ try {
+ Class< ? > clazz = loader.loadClass((String) token);
+ BundleActivator activator = (BundleActivator) clazz.newInstance();
+ embedded.add(activator);
+ trace("adding activator %s", activator);
+ }
+ catch (Exception e) {
+ throw new IllegalArgumentException("Embedded Bundle Activator incorrect: " + token + ", " + e);
+ }
+ }
+ }
+
+ update(System.currentTimeMillis() + 100);
+
+ if (parms.trace) {
+ report(out);
+ }
+
+ int result = LauncherConstants.OK;
+ for (BundleActivator activator : embedded)
+ try {
+ trace("starting activator %s", activator);
+ activator.start(systemContext);
+ }
+ catch (Exception e) {
+ error("Starting activator %s : %s", activator, e);
+ result = LauncherConstants.ERROR;
+ }
+
+ return result;
+}
+
public Collection<String> getRunVM() {
+ Parameters hdr = getParameters(RUNVM);
+ return hdr.keySet();
+}
+
+public int launch() throws Exception {
+ java = new Command();
+ java.add(project.getProperty("java", "java"));
+
+ java.add("-cp");
+ java.add(cp.toString());
+ java.addAll(project.getRunVM());
+ java.add(getMainTypeName());
+ java.addAll(fqns);
+ if (timeout != 0)
+ java.setTimeout(timeout + 1000, TimeUnit.MILLISECONDS);
+
+ project.trace("cmd line %s", java);
+ try {
+ int result = java.execute(System.in, System.err, System.err);
+ if (result == Integer.MIN_VALUE)
+ return TIMEDOUT;
+ reportResult(result);
+ return result;
+ }
+ finally {
+ cleanup();
+ }
+
+}
+
+
+ protected void report(Map<String,Object> table, boolean isProject) throws Exception {
+ if (isProject) {
+ table.put("Target", getTarget());
+ table.put("Source", getSrc());
+ table.put("Output", getOutput());
+ File[] buildFiles = getBuildFiles();
+ if (buildFiles != null)
+ table.put("BuildFiles", Arrays.asList(buildFiles));
+ table.put("Classpath", getClasspath());
+ table.put("Actions", getActions());
+ table.put("AllSourcePath", getAllsourcepath());
+ table.put("BootClassPath", getBootclasspath());
+ table.put("BuildPath", getBuildpath());
+ table.put("Deliverables", getDeliverables());
+ table.put("DependsOn", getDependson());
+ table.put("SourcePath", getSourcePath());
+ }
+ table.put("RunPath", getRunpath());
+ table.put("TestPath", getTestpath());
+ table.put("RunProgramArgs", getRunProgramArgs());
+ table.put("RunVM", getRunVM());
+ table.put("Runfw", getRunFw());
+ table.put("Runbundles", getRunbundles());
+}
+
public int launch() throws Exception { + prepare(); + java = new Command();
+ + //
+ // Handle the environment
+ //
+
+ Map<String,String> env = getRunEnv();
+ for ( Map.Entry<String,String> e:env.entrySet()) {
+ java.var(e.getKey(), e.getValue());
+ }
+
+ java.add(project.getProperty("java", "java"));
+ String javaagent = project.getProperty(Constants.JAVAAGENT);
+ if (Processor.isTrue(javaagent)) {
+ for (String agent : agents) {
+ java.add("-javaagent:" + agent);
+ }
+ }
+
+ String jdb = getRunJdb();
+ if (jdb != null) {
+ int port = 1044;
+ try {
+ port = Integer.parseInt(project.getProperty(Constants.RUNJDB));
+ }
+ catch (Exception e) {
+ // ok, value can also be ok, or on, or true
+ }
+ String suspend = port > 0 ? "y" : "n";
+
+ java.add("-Xrunjdwp:server=y,transport=dt_socket,address=" + Math.abs(port) + ",suspend=" + suspend);
+ }
+
+ java.add("-cp");
+ java.add(Processor.join(getClasspath(), File.pathSeparator));
+ java.addAll(getRunVM());
+ java.add(getMainTypeName());
+ java.addAll(getRunProgramArgs());
+ if (timeout != 0)
+ java.setTimeout(timeout + 1000, TimeUnit.MILLISECONDS);
+
+ File cwd = getCwd();
+ if (cwd != null)
+ java.setCwd(cwd);
+
+ project.trace("cmd line %s", java);
+ try {
+ int result = java.execute(System.in, System.err, System.err);
+ if (result == Integer.MIN_VALUE)
+ return TIMEDOUT;
+ reportResult(result);
+ return result;
+ }
+ finally {
+ cleanup();
+ listeners.clear();
+ }
+}
+
+public ProjectTester(Project project) throws Exception {
+ this.project = project;
+ launcher = project.getProjectLauncher();
+ launcher.addRunVM("-ea");
+ testbundles = project.getTestpath();
+ continuous = project.is(Constants.TESTCONTINUOUS);
+
+ for (Container c : testbundles) {
+ launcher.addClasspath(c);
+ }
+ reportDir = new File(project.getTarget(), project.getProperty("test-reports", "test-reports"));
+}
+
+
+ public ProjectLauncherImpl(Project project) throws Exception {
+ super(project);
+ project.trace("created a aQute launcher plugin");
+ this.project = project;
+ propertiesFile = File.createTempFile("launch", ".properties", project.getTarget());
+ project.trace(MessageFormat.format("launcher plugin using temp launch file {0}",
+ propertiesFile.getAbsolutePath()));
+ addRunVM("-D" + LauncherConstants.LAUNCHER_PROPERTIES + "=\"" + propertiesFile.getAbsolutePath() + "\"");
+
+ if (project.getRunProperties().get("noframework") != null) {
+ setRunFramework(NONE);
+ project.warning("The noframework property in -runproperties is replaced by a project setting: '-runframework: none'");
+ }
+
+ super.addDefault(Constants.DEFAULT_LAUNCHER_BSN);
+}
+
/**
+ * Get the manifest and write it out separately if -savemanifest is set
+ *
+ * @param dot
+ */
+private void doSaveManifest(Jar dot) throws Exception {
+ String output = getProperty(SAVEMANIFEST);
+ if (output == null)
+ return;
+
+ File f = getFile(output);
+ if (f.isDirectory()) {
+ f = new File(f, "MANIFEST.MF");
+ }
+ f.delete();
+ File fp = f.getParentFile();
+ if (!fp.exists() && !fp.mkdirs()) {
+ throw new IOException("Could not create directory " + fp);
+ }
+ OutputStream out = new FileOutputStream(f);
+ try {
+ Jar.writeManifest(dot.getManifest(), out);
+ }
+ finally {
+ out.close();
+ }
+ changedFile(f);
+}
+
void sign(@SuppressWarnings("unused")
+Jar jar) throws Exception {
+ String signing = getProperty(SIGN);
+ if (signing == null)
+ return;
+
+ trace("Signing %s, with %s", getBsn(), signing);
+ List<SignerPlugin> signers = getPlugins(SignerPlugin.class);
+
+ Parameters infos = parseHeader(signing);
+ for (Entry<String,Attrs> entry : infos.entrySet()) {
+ for (SignerPlugin signer : signers) {
+ signer.sign(this, entry.getKey());
+ }
+ }
+}
+
+
+/**
+ * Sign the current jar. The alias is the given certificate keystore.
+ *
+ * @param builder
+ * The current builder that contains the jar to sign
+ * @param alias
+ * The keystore certificate alias
+ * @throws Exception
+ * When anything goes wrong
+ */
+void sign(Builder builder, String alias) throws Exception;
+
+
+ /**
+ * Sign the jar file. -sign : <alias> [ ';' 'password:=' <password> ] [ ';'
+ * 'keystore:=' <keystore> ] [ ';' 'sign-password:=' <pw> ] ( ',' ... )*
+ *
+ * @author aqute
+ */
+ public class JartoolSigner implements Plugin, SignerPlugin {
+ String keystore;
+ String storetype;
+ String path = "jarsigner";
+ String storepass;
+ String keypass;
+ String sigFile;
+ String digestalg;
+
+ public void setProperties(Map<String,String> map) {
+ if (map.containsKey("keystore"))
+ this.keystore = map.get("keystore");
+ if (map.containsKey("storetype"))
+ this.storetype = map.get("storetype");
+ if (map.containsKey("storepass"))
+ this.storepass = map.get("storepass");
+ if (map.containsKey("keypass"))
+ this.keypass = map.get("keypass");
+ if (map.containsKey("path"))
+ this.path = map.get("path");
+ if (map.containsKey("sigFile"))
+ this.sigFile = map.get("sigFile");
+ if (map.containsKey("digestalg"))
+ this.digestalg = map.get("digestalg");
+ }
+
+ public void setReporter(Reporter processor) {}
+
+ public void sign(Builder builder, String alias) throws Exception {
+ File f = builder.getFile(keystore);
+ if (!f.isFile()) {
+ builder.error("Invalid keystore %s", f.getAbsolutePath());
+ return;
+ }
+
+ Jar jar = builder.getJar();
+ File tmp = File.createTempFile("signdjar", ".jar");
+ tmp.deleteOnExit();
+
+ jar.write(tmp);
+
+ Command command = new Command();
+ command.add(path);
+ if (keystore != null) {
+ command.add("-keystore");
+ command.add(f.getAbsolutePath());
+ }
+
+ if (storetype != null) {
+ command.add("-storetype");
+ command.add(storetype);
+ }
+
+ if (keypass != null) {
+ command.add("-keypass");
+ command.add(keypass);
+ }
+
+ if (storepass != null) {
+ command.add("-storepass");
+ command.add(storepass);
+ }
+
+ if (sigFile != null) {
+ command.add("-sigFile");
+ command.add(sigFile);
+ }
+
+ if (digestalg != null) {
+ command.add("-digestalg");
+ command.add(digestalg);
+ }
+
+ command.add(tmp.getAbsolutePath());
+ command.add(alias);
+ builder.trace("Jarsigner command: %s", command);
+ command.setTimeout(20, TimeUnit.SECONDS);
+ StringBuilder out = new StringBuilder();
+ StringBuilder err = new StringBuilder();
+ int exitValue = command.execute(out, err);
+ if (exitValue != 0) {
+ builder.error("Signing Jar out: %s\nerr: %s", out, err);
+ } else {
+ builder.trace("Signing Jar out: %s \nerr: %s", out, err);
+ }
+
+ Jar signed = new Jar(tmp);
+ builder.addClose(signed);
+
+ Map<String,Resource> dir = signed.getDirectories().get("META-INF");
+ for (Entry<String,Resource> entry : dir.entrySet()) {
+ String path = entry.getKey();
+ if (path.matches(".*\\.(DSA|RSA|SF|MF)$")) {
+ jar.putResource(path, entry.getValue());
+ }
+ }
+ jar.setDoNotTouchManifest();
+ }
+
+ StringBuilder collect(final InputStream in) throws Exception {
+ final StringBuilder sb = new StringBuilder();
+
+ Thread tin = new Thread() {
+ @Override
+ public void run() {
+ try {
+ BufferedReader rdr = new BufferedReader(new InputStreamReader(in, Constants.DEFAULT_CHARSET));
+ String line = rdr.readLine();
+ while (line != null) {
+ sb.append(line);
+ line = rdr.readLine();
+ }
+ rdr.close();
+ in.close();
+ }
+ catch (Exception e) {
+ // Ignore any exceptions
+ }
+ }
+ };
+ tin.start();
+ return sb;
+ }
+ }
+
When the bundle version’s qualifier equals “SNAPSHOT” or ends with “-SNAPSHOT”, the STRING
+value of the -snapshot
instruction is substituted for “SNAPSHOT”. The STRING value of
+the empty string will remove the qualifier if it equals “SNAPSHOT” or
+will remove “-SNAPSHOT” from the end of the qualifier.
The default is no substitution.
+ +For example, the bundle’s version can be set with a “SNAPSHOT” qualifier indicating it is +a maven snapshot version.
+ +Bundle-Version: 3.2.0.SNAPSHOT
+
When time to release, the -snapshot
instruction can be used, in a central location such
+as cnf/build.bnd
, to substitute “SNAPSHOT” for another value such as the timestamp of
+the build:
-snapshot: ${tstamp}
+
or some other meaningful value:
+ +-snapshot: REL
+
Using “SNAPSHOT” in the bundle version’s qualifier and the -snapshot
instruction
+are generally for bundles intended for maven builds.
public Collection<File> getSourcePath() {
+ if (firstUse) {
+ firstUse = false;
+ String sp = getProperty(SOURCEPATH);
+ if (sp != null) {
+ Parameters map = parseHeader(sp);
+ for (Iterator<String> i = map.keySet().iterator(); i.hasNext();) {
+ String file = i.next();
+ if (!isDuplicate(file)) {
+ File f = getFile(file);
+ if (!f.isDirectory()) {
+ error("Adding a sourcepath that is not a directory: " + f);
+ } else {
+ sourcePath.add(f);
+ }
+ }
+ }
+ }
+ }
+ return sourcePath;
+}
+
+private void addSources(Jar dot) {
+ if (!hasSources())
+ return;
+
+ Set<PackageRef> packages = Create.set();
+
+ for (TypeRef typeRef : getClassspace().keySet()) {
+ PackageRef packageRef = typeRef.getPackageRef();
+ String sourcePath = typeRef.getSourcePath();
+ String packagePath = packageRef.getPath();
+
+ boolean found = false;
+ String[] fixed = {
+ "packageinfo", "package.html", "module-info.java", "package-info.java"
+ };
+
+ for (Iterator<File> i = getSourcePath().iterator(); i.hasNext();) {
+ File root = i.next();
+
+ // TODO should use bcp?
+
+ File f = getFile(root, sourcePath);
+ if (f.exists()) {
+ found = true;
+ if (!packages.contains(packageRef)) {
+ packages.add(packageRef);
+ File bdir = getFile(root, packagePath);
+ for (int j = 0; j < fixed.length; j++) {
+ File ff = getFile(bdir, fixed[j]);
+ if (ff.isFile()) {
+ String name = "OSGI-OPT/src/" + packagePath + "/" + fixed[j];
+ dot.putResource(name, new FileResource(ff));
+ }
+ }
+ }
+ if (packageRef.isDefaultPackage())
+ System.err.println("Duh?");
+ dot.putResource("OSGI-OPT/src/" + sourcePath, new FileResource(f));
+ }
+ }
+ if (!found) {
+ for (Jar jar : getClasspath()) {
+ Resource resource = jar.getResource(sourcePath);
+ if (resource != null) {
+ dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
+ } else {
+ resource = jar.getResource("OSGI-OPT/src/" + sourcePath);
+ if (resource != null) {
+ dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
+ }
+ }
+ }
+ }
+ if (getSourcePath().isEmpty())
+ warning("Including sources but " + SOURCEPATH + " does not contain any source directories ");
+ // TODO copy from the jars where they came from
+ }
+}
+
public Resource make(Builder builder, String destination, Map<String,String> argumentsOnMake) throws Exception {
+ String type = argumentsOnMake.get("type");
+ if (!"bnd".equals(type))
+ return null;
+
+ String recipe = argumentsOnMake.get("recipe");
+ if (recipe == null) {
+ builder.error("No recipe specified on a make instruction for " + destination);
+ return null;
+ }
+ File bndfile = builder.getFile(recipe);
+ if (bndfile.isFile()) {
+ // We do not use a parent because then we would
+ // build ourselves again. So we can not blindly
+ // inherit the properties.
+ Builder bchild = builder.getSubBuilder();
+ bchild.removeBundleSpecificHeaders();
+
+ // We must make sure that we do not include ourselves again!
+ bchild.setProperty(Analyzer.INCLUDE_RESOURCE, "");
+ bchild.setProperty(Analyzer.INCLUDERESOURCE, "");
+ bchild.setProperties(bndfile, builder.getBase());
+
+ Jar jar = bchild.build();
+ Jar dot = builder.getTarget();
+
+ if (builder.hasSources()) {
+ for (String key : jar.getResources().keySet()) {
+ if (key.startsWith("OSGI-OPT/src"))
+ dot.putResource(key, jar.getResource(key));
+ }
+ }
+ builder.getInfo(bchild, bndfile.getName() + ": ");
+ return new JarResource(jar);
+ }
+ return null;
+}
+
+ /**
+ *
+ */
+private void addSources(Jar dot) {
+ if (!hasSources())
+ return;
+
+ Set<PackageRef> packages = Create.set();
+
+ for (TypeRef typeRef : getClassspace().keySet()) {
+ PackageRef packageRef = typeRef.getPackageRef();
+ String sourcePath = typeRef.getSourcePath();
+ String packagePath = packageRef.getPath();
+
+ boolean found = false;
+ String[] fixed = {
+ "packageinfo", "package.html", "module-info.java", "package-info.java"
+ };
+
+ for (Iterator<File> i = getSourcePath().iterator(); i.hasNext();) {
+ File root = i.next();
+
+ // TODO should use bcp?
+
+ File f = getFile(root, sourcePath);
+ if (f.exists()) {
+ found = true;
+ if (!packages.contains(packageRef)) {
+ packages.add(packageRef);
+ File bdir = getFile(root, packagePath);
+ for (int j = 0; j < fixed.length; j++) {
+ File ff = getFile(bdir, fixed[j]);
+ if (ff.isFile()) {
+ String name = "OSGI-OPT/src/" + packagePath + "/" + fixed[j];
+ dot.putResource(name, new FileResource(ff));
+ }
+ }
+ }
+ if (packageRef.isDefaultPackage())
+ System.err.println("Duh?");
+ dot.putResource("OSGI-OPT/src/" + sourcePath, new FileResource(f));
+ }
+ }
+ if (!found) {
+ for (Jar jar : getClasspath()) {
+ Resource resource = jar.getResource(sourcePath);
+ if (resource != null) {
+ dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
+ } else {
+ resource = jar.getResource("OSGI-OPT/src/" + sourcePath);
+ if (resource != null) {
+ dot.putResource("OSGI-OPT/src/" + sourcePath, resource);
+ }
+ }
+ }
+ }
+ if (getSourcePath().isEmpty())
+ warning("Including sources but " + SOURCEPATH + " does not contain any source directories ");
+ // TODO copy from the jars where they came from
+ }
+}
+
+ /**
+ * Cop
+ *
+ * @param dest
+ * @param srce
+ * @param path
+ * @param overwriteResource
+ */
+private void copy(Jar dest, Jar srce, String path, boolean overwrite) {
+ trace("copy d=" + dest + " s=" + srce + " p=" + path);
+ dest.copy(srce, path, overwrite);
+
+ // bnd.info sources must be preprocessed
+ String bndInfoPath = path + "/bnd.info";
+ Resource r = dest.getResource(bndInfoPath);
+ if (r != null && !(r instanceof PreprocessResource)) {
+ trace("preprocessing bnd.info");
+ PreprocessResource pp = new PreprocessResource(this, r);
+ dest.putResource(bndInfoPath, pp);
+ }
+
+ if (hasSources()) {
+ String srcPath = "OSGI-OPT/src/" + path;
+ Map<String,Resource> srcContents = srce.getDirectories().get(srcPath);
+ if (srcContents != null) {
+ dest.addDirectory(srcContents, overwrite);
+ }
+ }
+}
+
If you work in Bndtools then there is normally little to worry about, bndtools keeps everything up to dated all the time. +Any file you save, and guaranteed in s short time all dependent bundles are updated. This comes with a crucial guarantee +that anything you do in Bndtools is faithfully reproduced on the build server using Gradle. For a default workspace there +is no need for any gradle specific files. All the workspace’s settings are defined in the bnd files.
+ +However, sometimes it is necessary to generate sources that need to be compiled. There are then two choices when you +checkout a workspace. Always use the command line let the build do its magic or checkin the generated sources. +Although generally frowned upon, it has the advantage that you do not need to store the tool itself under version control. +Any revision can always compile regardless of what happened to the tool. It is also nice to see it on Github, it actually +makes the experience of the majority of people that use the workspace and usually not touch the swagger file blithely +unaware of the underlying complexities.
+ +However, this has a very serious problem. Sometimes you touch the swagger file and forget to run the gradle to generate
+new sources. It would be very convenient if bnd could generate an error if certain files were out of date respective
+to a set of other files. Thhe -stalecheck
instruction has this purpose.
-stalecheck ::= clause ( ',' clause )*
+clause ::= src ';' 'newer=' depends (';' option )*
+src ::= Ant Glob spec
+depends ::= Ant Glob spec
+option ::= 'error=' STRING
+ | 'warning=' STRING
+ | 'command=' STRING
+
Just before bnd will start making the JAR(s) for a project it will check if any of the files in the src
specification
+is newer than the files in the depends
set. If so, it will provide warning or error. If either a specific error or
+warning STRING
is given then this is reported accordingly.
If a command STRING
is given it is executed as in the ${system}
macro. If the command STRING
starts with a
+minus sign (-
) then a failure is not an error, it is reported as warning.
If no command is given, nor an error or warning then a default error is reported.
+ +-stalecheck: \
+ openapi.json; \
+ newer='gen-src/, checksum.md5'
+
+-stalecheck: \
+ specs/**.md; \
+ newer='doc/**.doc' ; \
+ error='Markdown needs to be generated'
+
The -standalone
instruction in bnd allows you to transform a bndrun file into a standalone file that doesn’t require a workspace. It is a merged property, meaning you can specify additional repo-specs
using -standalone.extra
. However, the presence of the exact -standalone
flag is what determines if the bndrun file is standalone. Without the -standalone
flag, even if -standalone.extra
is specified, a workspace will still be required.
A bndrun
file is by default connected to its workspace, where the workspace defines the context and most important: the repositories. The workspace is by default defined in the workspace’s cnf
directory.
The -standalone
instruction tells bnd that this connection should be severed and that all information is contained in the bndrun
file. The value of the -standalone
instruction is used to define the repositories. Each repo-spec
clause defines a repository.
-standalone ::= repo-spec ( ',' repo-spec )*
+repo-spec ::= <relative url> ( '; <fixed index repo attrs> )*
+
The repositories that are created from the -standalone
instruction are the OSGi Capabilities repositories as implemented by the Fixed Index Repository. For this repository you can add the following attributes:
name
– Name of the repositorycache
– File path to a cache directorytimeout
– Time out for downloading the index-standalone: index.html; name=local
+-runfw: org.apache.felix.framework;version=5
+-runee: JavaSE-1.8
+-runrequires: osgi.identity;filter:='(osgi.identity=com.springsource.org.apache.tools.ant)'
+
/**
+ * If strict is true, then extra verification is done.
+ */
+boolean isStrict() {
+ if (strict == null)
+ strict = isTrue(getProperty(STRICT)); // Used in property access
+ return strict;
+}
+
/**
+ * Answer a list of builders that represent this file or a list of files
+ * specified in -sub. This list can be empty. These builders represents to
+ * be created artifacts and are each scoped to such an artifacts. The
+ * builders can be used to build the bundles or they can be used to find out
+ * information about the to be generated bundles.
+ *
+ * @return List of 0..n builders representing artifacts.
+ * @throws Exception
+ */
+public List<Builder> getSubBuilders() throws Exception {
+ String sub = getProperty(SUB);
+ if (sub == null || sub.trim().length() == 0 || EMPTY_HEADER.equals(sub))
+ return Arrays.asList(this);
+
+ List<Builder> builders = new ArrayList<Builder>();
+ if (isTrue(getProperty(NOBUNDLES)))
+ return builders;
+
+ Parameters subsMap = parseHeader(sub);
+ for (Iterator<String> i = subsMap.keySet().iterator(); i.hasNext();) {
+ File file = getFile(i.next());
+ if (file.isFile() && !file.getName().startsWith(".")) {
+ builders.add(getSubBuilder(file));
+ i.remove();
+ }
+ }
+
+ Instructions instructions = new Instructions(subsMap);
+
+ List<File> members = new ArrayList<File>(Arrays.asList(getBase().listFiles()));
+
+ nextFile: while (members.size() > 0) {
+
+ File file = members.remove(0);
+
+ // Check if the file is one of our parents
+ @SuppressWarnings("resource")
+ Processor p = this;
+ while (p != null) {
+ if (file.equals(p.getPropertiesFile()))
+ continue nextFile;
+ p = p.getParent();
+ }
+
+ for (Iterator<Instruction> i = instructions.keySet().iterator(); i.hasNext();) {
+
+ Instruction instruction = i.next();
+ if (instruction.matches(file.getName())) {
+
+ if (!instruction.isNegated()) {
+ builders.add(getSubBuilder(file));
+ }
+
+ // Because we matched (even though we could be negated)
+ // we skip any remaining searches
+ continue nextFile;
+ }
+ }
+ }
+ return builders;
+}
+
+public Builder getSubBuilder(File file) throws Exception {
+ Builder builder = getSubBuilder();
+ if (builder != null) {
+ builder.setProperties(file);
+ addClose(builder);
+ }
+ return builder;
+}
+
+public Builder getSubBuilder() throws Exception {
+ Builder builder = new Builder(this);
+ builder.setBase(getBase());
+
+ for (Jar file : getClasspath()) {
+ builder.addClasspath(file);
+ }
+
+ return builder;
+}
+
public Workspace(File dir, String bndDir) throws Exception {
+ super(getDefaults());
+ dir = dir.getAbsoluteFile();
+ if (!dir.exists() && !dir.mkdirs()) {
+ throw new IOException("Could not create directory " + dir);
+ }
+ assert dir.isDirectory();
+
+ File buildDir = new File(dir, bndDir).getAbsoluteFile();
+ if (!buildDir.isDirectory())
+ buildDir = new File(dir, CNFDIR).getAbsoluteFile();
+
+ this.buildDir = buildDir;
+
+ File buildFile = new File(buildDir, BUILDFILE).getAbsoluteFile();
+ if (!buildFile.isFile())
+ warning("No Build File in " + dir);
+
+ setProperties(buildFile, dir);
+ propertiesChanged();
+
+ //
+ // There is a nasty bug/feature in Java that gives errors on our
+ // SSL use of github. The flag jsse.enableSNIExtension should be set
+ // to false. So here we provide a way to set system properties
+ // as early as possible
+ //
+
+ Attrs sysProps = OSGiHeader.parseProperties(getProperty(SYSTEMPROPERTIES));
+ for (Entry<String,String> e : sysProps.entrySet()) {
+ System.setProperty(e.getKey(), e.getValue());
+ }
+
+}
+
public void test(List<String> tests) throws Exception {
+
+ String testcases = getProperties().getProperty(Constants.TESTCASES);
+ if (testcases == null) {
+ warning("No %s set", Constants.TESTCASES);
+ return;
+ }
+ clear();
+
+ ProjectTester tester = getProjectTester();
+ if ( tests != null) {
+ trace("Adding tests %s", tests);
+ for ( String test : tests) {
+ tester.addTest(test);
+ }
+ }
+ tester.setContinuous(isTrue(getProperty(Constants.TESTCONTINUOUS)));
+ tester.prepare();
+
+ if (!isOk()) {
+ return;
+ }
+ int errors = tester.test();
+ if (errors == 0) {
+ System.err.println("No Errors");
+ } else {
+ if (errors > 0) {
+ System.err.println(errors + " Error(s)");
+
+ } else
+ System.err.println("Error " + errors);
+ }
+}
+
+
+public ProjectTester(Project project) throws Exception {
+ this.project = project;
+ launcher = project.getProjectLauncher();
+ launcher.addRunVM("-ea");
+ testbundles = project.getTestpath();
+ continuous = project.is(Constants.TESTCONTINUOUS);
+
+ for (Container c : testbundles) {
+ launcher.addClasspath(c);
+ }
+ reportDir = new File(project.getTarget(), project.getProperty("test-reports", "test-reports"));
+}
+
+ @Description("Test a project according to an OSGi test")
+@Arguments(arg = {
+ "testclass[:method]..."
+})
+interface testOptions extends Options {
+ @Description("Path to another project than the current project")
+ String project();
+
+ @Description("Verify all the dependencies before launching (runpath, runbundles, testpath)")
+ boolean verify();
+
+ @Description("Launch the test even if this bundle does not contain " + Constants.TESTCASES)
+ boolean force();
+
+ @Description("Set the -testcontinuous flag")
+ boolean continuous();
+
+ @Description("Set the -runtrace flag")
+ boolean trace();
+}
+
+@Description("Test a project according to an OSGi test")
+public void _test(testOptions opts) throws Exception {
+ Project project = getProject(opts.project());
+ if (project == null) {
+ messages.NoProject();
+ return;
+ }
+
+ // if (!verifyDependencies(project, opts.verify(), true))
+ // return;
+ //
+ List<String> testNames = opts._();
+ if (!testNames.isEmpty())
+ project.setProperty(TESTCASES, "");
+
+ if (project.is(NOJUNITOSGI) && !opts.force()) {
+ warning("%s is set to true on this bundle. Use -f/--force to try this test anyway", NOJUNITOSGI);
+ return;
+ }
+
+ if (project.getProperty(TESTCASES) == null)
+ if (opts.force())
+ project.setProperty(TESTCASES, "");
+ else {
+ warning("No %s set on this bundle. Use -f/--force to try this test anyway (this works if another bundle provides the testcases)",
+ TESTCASES);
+ return;
+ }
+
+ if (opts.continuous())
+ project.setProperty(TESTCONTINUOUS, "true");
+
+ if (opts.trace() || isTrace())
+ project.setProperty(RUNTRACE, "true");
+
+ project.test(testNames);
+ getInfo(project);
+}
+
+ @Override
+public boolean prepare() throws Exception {
+ if (!prepared) {
+ prepared = true;
+ super.prepare();
+ ProjectLauncher launcher = getProjectLauncher();
+ if (port > 0) {
+ launcher.getRunProperties().put(TESTER_PORT, "" + port);
+ if (host != null)
+ launcher.getRunProperties().put(TESTER_HOST, "" + host);
+
+ }
+ launcher.getRunProperties().put(TESTER_UNRESOLVED, project.getProperty(Constants.TESTUNRESOLVED, "true"));
+
+ launcher.getRunProperties().put(TESTER_DIR, getReportDir().getAbsolutePath());
+ launcher.getRunProperties().put(TESTER_CONTINUOUS, "" + getContinuous());
+ if (Processor.isTrue(project.getProperty(Constants.RUNTRACE)))
+ launcher.getRunProperties().put(TESTER_TRACE, "true");
+
+ try {
+ // use reflection to avoid NoSuchMethodError due to change in
+ // API
+ File cwd = (File) getClass().getMethod("getCwd").invoke(this);
+ if (cwd != null)
+ launcher.setCwd(cwd);
+ }
+ catch (NoSuchMethodException e) {
+ // ignore
+ }
+
+ Collection<String> testnames = getTests();
+ if (testnames.size() > 0) {
+ launcher.getRunProperties().put(TESTER_NAMES, Processor.join(testnames));
+ }
+ // This is only necessary because we might be picked
+ // as default and that implies we're not on the -testpath
+ launcher.addDefault(Constants.DEFAULT_TESTER_BSN);
+ launcher.prepare();
+ }
+ return true;
+}
+
+
+ public void run() {
+
+ continuous = Boolean.valueOf(context.getProperty(TESTER_CONTINUOUS));
+ trace = context.getProperty(TESTER_TRACE) != null;
+
+ if (thread == null)
+ trace("running in main thread");
+
+ // We can be started on our own thread or from the main code
+ thread = Thread.currentThread();
+
+
+ String testcases = context.getProperty(TESTER_NAMES);
+ trace("test cases %s", testcases);
+ if (context.getProperty(TESTER_PORT) != null) {
+ port = Integer.parseInt(context.getProperty(TESTER_PORT));
+ try {
+ trace("using port %s", port);
+ jUnitEclipseReport = new JUnitEclipseReport(port);
+ }
+ catch (Exception e) {
+ System.err.println("Cannot create link Eclipse JUnit on port " + port);
+ System.exit(254);
+ }
+ }
+
+
+ //
+ // Jenkins does not detect test failures unless reported
+ // by JUnit XML output. If we have an unresolved failure
+ // we timeout. The following will test if there are any
+ // unresolveds and report this as a JUnit failure. It can
+ // be disabled with -testunresolved=false
+ //
+
+ String unresolved = context.getProperty(TESTER_UNRESOLVED);
+ trace("run unresolved %s", unresolved);
+
+ if (unresolved == null || unresolved.equalsIgnoreCase("true")) {
+ //
+ // Check if there are any unresolved bundles.
+ // If yes, we run a test case to get a proper JUnit report
+ //
+ for ( Bundle b : context.getBundles()) {
+ if ( b.getState() == Bundle.INSTALLED) {
+ //
+ // Now do it again but as a test case
+ // so we get a proper JUnit report
+ //
+ int err = test(context.getBundle(), "aQute.junit.UnresolvedTester", null);
+ if (err != 0)
+ System.exit(err);
+ }
+ }
+ }
+
+ if (testcases == null) { // if ( !continuous) { // System.err.println("\nThe -testcontinuous property must be set if invoked without arguments\n"); // System.exit(255); // }
+
+ trace("automatic testing of all bundles with " + aQute.bnd.osgi.Constants.TESTCASES + " header");
+ try {
+ automatic();
+ }
+ catch (IOException e) {
+ // ignore
+ }
+ } else {
+ trace("receivednames of classes to test %s", testcases);
+ try {
+ int errors = test(null, testcases, null);
+ System.exit(errors);
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ System.exit(254);
+ }
+ }
+}
+
The -tester
instruction defines what bundle is used to setup testing. This bundle must have a Tester-Plugin that will setup the test environment.
By default the, -tester
is the bundle biz.aQute.tester
. This bundle will instruct bnd to add this bundle to the -runbundles
, sets the appropriate properties, and then launches. It will then run whatever tests are configured.
For a long time bnd had biz.aQute.junit as the tester. This launcher added itself to the -runpath
and then executed the tests from there. Unfortunately this required that the tester actually exported the JUnit packages. This caused constraints between JUnit and bnd that was not good because JUnit itself is not directly a shining example of software engineering :-(
If you want to be backward compatible with the older model, set:
+ +-tester: biz.aQute.junit
+
private void doExpand(Jar dot) {
+
+ // Build an index of the class path that we can then
+ // use destructively
+ MultiMap<String,Jar> packages = new MultiMap<String,Jar>();
+ for (Jar srce : getClasspath()) {
+ dot.updateModified(srce.lastModified, srce + " (" + srce.lastModifiedReason + ")");
+ for (Entry<String,Map<String,Resource>> e : srce.getDirectories().entrySet()) {
+ if (e.getValue() != null)
+ packages.add(e.getKey(), srce);
+ }
+ }
+
+ Parameters privatePackages = getPrivatePackage();
+ if (isTrue(getProperty(Constants.UNDERTEST))) {
+ String h = getProperty(Constants.TESTPACKAGES, "test;presence:=optional");
+ privatePackages.putAll(parseHeader(h));
+ }
+
+ if (!privatePackages.isEmpty()) {
+ Instructions privateFilter = new Instructions(privatePackages);
+ Set<Instruction> unused = doExpand(dot, packages, privateFilter);
+
+ if (!unused.isEmpty()) {
+ warning("Unused " + Constants.PRIVATE_PACKAGE + " instructions, no such package(s) on the class path: %s", unused);
+ }
+ }
+
+ Parameters exportedPackage = getExportPackage();
+ if (!exportedPackage.isEmpty()) {
+ Instructions exportedFilter = new Instructions(exportedPackage);
+
+ // We ignore unused instructions for exports, they should show
+ // up as errors during analysis. Otherwise any overlapping
+ // packages with the private packages should show up as
+ // unused
+
+ doExpand(dot, packages, exportedFilter);
+ }
+}
+
+ /**
+ * Check if the given resource is in scope of this bundle. That is, it
+ * checks if the Include-Resource includes this resource or if it is a class
+ * file it is on the class path and the Export-Package or Private-Package
+ * include this resource.
+ *
+ * @param f
+ * @return
+ */
+public boolean isInScope(Collection<File> resources) throws Exception {
+ Parameters clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
+ clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
+ clauses.putAll(parseHeader(getProperty(Constants.PRIVATEPACKAGE)));
+ if (isTrue(getProperty(Constants.UNDERTEST))) {
+ clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES, "test;presence:=optional")));
+ }
+
+ Collection<String> ir = getIncludedResourcePrefixes();
+
+ Instructions instructions = new Instructions(clauses);
+
+ for (File r : resources) {
+ String cpEntry = getClasspathEntrySuffix(r);
+
+ if (cpEntry != null) {
+
+ if (cpEntry.equals("")) // Meaning we actually have a CPE
+ return true;
+
+ String pack = Descriptors.getPackage(cpEntry);
+ Instruction i = matches(instructions, pack, null, r.getName());
+ if (i != null)
+ return !i.isNegated();
+ }
+
+ // Check if this resource starts with one of the I-C header
+ // paths.
+ String path = r.getAbsolutePath();
+ for (String p : ir) {
+ if (path.startsWith(p))
+ return true;
+ }
+ }
+ return false;
+}
+
public Collection<Container> getTestpath() throws Exception {
+ prepare();
+ justInTime(testpath, parseTestpath(), false, TESTPATH);
+ return testpath;
+}
+
+private List<Container> parseTestpath() throws Exception {
+ return getBundles(Strategy.HIGHEST, mergeProperties(Constants.TESTPATH), Constants.TESTPATH);
+}
+
+ doPath(buildpath, dependencies, parseBuildpath(), bootclasspath, false, BUILDPATH);
+ doPath(testpath, dependencies, parseTestpath(), bootclasspath, false, TESTPATH);
+ if (!delayRunDependencies) {
+ doPath(runfw, dependencies, parseRunFw(), null, false, RUNFW);
+ doPath(runpath, dependencies, parseRunpath(), null, false, RUNPATH);
+ doPath(runbundles, dependencies, parseRunbundles(), null, true, RUNBUNDLES);
+ }
+/**
+ * Method to verify that the paths are correct, ie no missing dependencies
+ *
+ * @param test
+ * for test cases, also adds -testpath
+ * @throws Exception
+ */
+public void verifyDependencies(boolean test) throws Exception {
+ verifyDependencies(RUNBUNDLES, getRunbundles());
+ verifyDependencies(RUNPATH, getRunpath());
+ if (test)
+ verifyDependencies(TESTPATH, getTestpath());
+ verifyDependencies(BUILDPATH, getBuildpath());
+}
+
+ public void prepare() throws Exception {
+ Pattern tests = Pattern.compile(project.getProperty(Constants.TESTSOURCES, "(.*).java"));
+
+ String testDirName = project.getProperty("testsrc", "test");
+ File testSrc = project.getFile(testDirName).getAbsoluteFile();
+ if (!testSrc.isDirectory()) {
+ project.trace("no test src directory");
+ return;
+ }
+
+ if (!traverse(fqns, testSrc, "", tests)) {
+ project.trace("no test files found in %s", testSrc);
+ return;
+ }
+
+ timeout = Processor.getDuration(project.getProperty(Constants.RUNTIMEOUT), 0); // trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
+ cp = new Classpath(project, "junit");
+ addClasspath(project.getTestpath());
+ addClasspath(project.getBuildpath());
+}
+
+
+public void compile(boolean test) throws Exception {
+
+ Command javac = getCommonJavac(false);
+ javac.add("-d", getOutput().getAbsolutePath());
+
+ StringBuilder buildpath = new StringBuilder();
+
+ String buildpathDel = "";
+ Collection<Container> bp = Container.flatten(getBuildpath());
+ trace("buildpath %s", getBuildpath());
+ for (Container c : bp) {
+ buildpath.append(buildpathDel).append(c.getFile().getAbsolutePath());
+ buildpathDel = File.pathSeparator;
+ }
+
+ if (buildpath.length() != 0) {
+ javac.add("-classpath", buildpath.toString());
+ }
+
+ List<File> sp = new ArrayList<File>(getAllsourcepath());
+ StringBuilder sourcepath = new StringBuilder();
+ String sourcepathDel = "";
+
+ for (File sourceDir : sp) {
+ sourcepath.append(sourcepathDel).append(sourceDir.getAbsolutePath());
+ sourcepathDel = File.pathSeparator;
+ }
+
+ javac.add("-sourcepath", sourcepath.toString());
+
+ Glob javaFiles = new Glob("*.java");
+ List<File> files = javaFiles.getFiles(getSrc(), true, false);
+
+ for (File file : files) {
+ javac.add(file.getAbsolutePath());
+ }
+
+ if (files.isEmpty()) {
+ trace("Not compiled, no source files");
+ } else
+ compile(javac, "src");
+
+ if (test) {
+ javac = getCommonJavac(true);
+ javac.add("-d", getTestOutput().getAbsolutePath());
+
+ Collection<Container> tp = Container.flatten(getTestpath());
+ for (Container c : tp) {
+ buildpath.append(buildpathDel).append(c.getFile().getAbsolutePath());
+ buildpathDel = File.pathSeparator;
+ }
+ if (buildpath.length() != 0) {
+ javac.add("-classpath", buildpath.toString());
+ }
+
+ sourcepath.append(sourcepathDel).append(getTestSrc().getAbsolutePath());
+ javac.add("-sourcepath", sourcepath.toString());
+
+ javaFiles.getFiles(getTestSrc(), files, true, false);
+ for (File file : files) {
+ javac.add(file.getAbsolutePath());
+ }
+ if (files.isEmpty()) {
+ trace("Not compiled for test, no test src files");
+ } else
+ compile(javac, "test");
+ }
+}
+
+
+
+ public ProjectTester getProjectTester() throws Exception {
+ return getHandler(ProjectTester.class, getTestpath(), TESTER_PLUGIN, "biz.aQute.junit");
+}
+
+
+
+ public ProjectTester(Project project) throws Exception {
+ this.project = project;
+ launcher = project.getProjectLauncher();
+ launcher.addRunVM("-ea");
+ testbundles = project.getTestpath();
+ continuous = project.is(Constants.TESTCONTINUOUS);
+
+ for (Container c : testbundles) {
+ launcher.addClasspath(c);
+ }
+ reportDir = new File(project.getTarget(), project.getProperty("test-reports", "test-reports"));
+}
+
TODO this is not working yet
+ +public void prepare() throws Exception {
+ Pattern tests = Pattern.compile(project.getProperty(Constants.TESTSOURCES, "(.*).java"));
+
+ String testDirName = project.getProperty("testsrc", "test");
+ File testSrc = project.getFile(testDirName).getAbsoluteFile();
+ if (!testSrc.isDirectory()) {
+ project.trace("no test src directory");
+ return;
+ }
+
+ if (!traverse(fqns, testSrc, "", tests)) {
+ project.trace("no test files found in %s", testSrc);
+ return;
+ }
+
+ timeout = Processor.getDuration(project.getProperty(Constants.RUNTIMEOUT), 0); // trace = Processor.isTrue(project.getProperty(Constants.RUNTRACE));
+ cp = new Classpath(project, "junit");
+ addClasspath(project.getTestpath());
+ addClasspath(project.getBuildpath());
+}
+
@Override
+public boolean prepare() throws Exception {
+ if (!prepared) {
+ prepared = true;
+ super.prepare();
+ ProjectLauncher launcher = getProjectLauncher();
+ if (port > 0) {
+ launcher.getRunProperties().put(TESTER_PORT, "" + port);
+ if (host != null)
+ launcher.getRunProperties().put(TESTER_HOST, "" + host);
+
+ }
+ launcher.getRunProperties().put(TESTER_UNRESOLVED, project.getProperty(Constants.TESTUNRESOLVED, "true"));
+
+ launcher.getRunProperties().put(TESTER_DIR, getReportDir().getAbsolutePath());
+ launcher.getRunProperties().put(TESTER_CONTINUOUS, "" + getContinuous());
+ if (Processor.isTrue(project.getProperty(Constants.RUNTRACE)))
+ launcher.getRunProperties().put(TESTER_TRACE, "true");
+
+ try {
+ // use reflection to avoid NoSuchMethodError due to change in
+ // API
+ File cwd = (File) getClass().getMethod("getCwd").invoke(this);
+ if (cwd != null)
+ launcher.setCwd(cwd);
+ }
+ catch (NoSuchMethodException e) {
+ // ignore
+ }
+
+ Collection<String> testnames = getTests();
+ if (testnames.size() > 0) {
+ launcher.getRunProperties().put(TESTER_NAMES, Processor.join(testnames));
+ }
+ // This is only necessary because we might be picked
+ // as default and that implies we're not on the -testpath
+ launcher.addDefault(Constants.DEFAULT_TESTER_BSN);
+ launcher.prepare();
+ }
+ return true;
+}
+
+
+ public void run() {
+
+ continuous = Boolean.valueOf(context.getProperty(TESTER_CONTINUOUS));
+ trace = context.getProperty(TESTER_TRACE) != null;
+
+ if (thread == null)
+ trace("running in main thread");
+
+ // We can be started on our own thread or from the main code
+ thread = Thread.currentThread();
+
+
+ String testcases = context.getProperty(TESTER_NAMES);
+ trace("test cases %s", testcases);
+ if (context.getProperty(TESTER_PORT) != null) {
+ port = Integer.parseInt(context.getProperty(TESTER_PORT));
+ try {
+ trace("using port %s", port);
+ jUnitEclipseReport = new JUnitEclipseReport(port);
+ }
+ catch (Exception e) {
+ System.err.println("Cannot create link Eclipse JUnit on port " + port);
+ System.exit(254);
+ }
+ }
+
+
+ //
+ // Jenkins does not detect test failures unless reported
+ // by JUnit XML output. If we have an unresolved failure
+ // we timeout. The following will test if there are any
+ // unresolveds and report this as a JUnit failure. It can
+ // be disabled with -testunresolved=false
+ //
+
+ String unresolved = context.getProperty(TESTER_UNRESOLVED);
+ trace("run unresolved %s", unresolved);
+
+ if (unresolved == null || unresolved.equalsIgnoreCase("true")) {
+ //
+ // Check if there are any unresolved bundles.
+ // If yes, we run a test case to get a proper JUnit report
+ //
+ for ( Bundle b : context.getBundles()) {
+ if ( b.getState() == Bundle.INSTALLED) {
+ //
+ // Now do it again but as a test case
+ // so we get a proper JUnit report
+ //
+ int err = test(context.getBundle(), "aQute.junit.UnresolvedTester", null);
+ if (err != 0)
+ System.exit(err);
+ }
+ }
+ }
+
+ if (testcases == null) { // if ( !continuous) { // System.err.println("\nThe -testcontinuous property must be set if invoked without arguments\n"); // System.exit(255); // }
+
+ trace("automatic testing of all bundles with " + aQute.bnd.osgi.Constants.TESTCASES + " header");
+ try {
+ automatic();
+ }
+ catch (IOException e) {
+ // ignore
+ }
+ } else {
+ trace("receivednames of classes to test %s", testcases);
+ try {
+ int errors = test(null, testcases, null);
+ System.exit(errors);
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ System.exit(254);
+ }
+ }
+}
+
/** + * Build without doing any dependency checking. Make sure any dependent + * projects are built first. + * + * @param underTest + * @return + * @throws Exception + */ + public File[] buildLocal(boolean underTest) throws Exception { + if (isNoBundles()) + return null;
+ + File bfs = new File(getTarget(), BUILDFILES);
+ bfs.delete();
+
+ files = null;
+ ProjectBuilder builder = getBuilder(null);
+ try {
+ if (underTest)
+ builder.setProperty(Constants.UNDERTEST, "true");
+ Jar jars[] = builder.builds();
+ File[] files = new File[jars.length];
+
+ getInfo(builder);
+
+ if (isOk()) {
+ this.files = files;
+
+ for (int i = 0; i < jars.length; i++) {
+ Jar jar = jars[i];
+ File file = saveBuild(jar);
+ if (file == null) {
+ getInfo(builder);
+ error("Could not save %s", jar.getName());
+ return this.files = null;
+ }
+ this.files[i] = file;
+ }
+
+ // Write out the filenames in the buildfiles file
+ // so we can get them later evenin another process
+ Writer fw = IO.writer(bfs);
+ try {
+ for (File f : files) {
+ fw.append(f.getAbsolutePath());
+ fw.append("\n");
+ }
+ }
+ finally {
+ fw.close();
+ }
+ getWorkspace().changedFile(bfs);
+ return files;
+ }
+ return null;
+ }
+ finally {
+ builder.close();
+ }
+}
+
+
+ private void doExpand(Jar dot) {
+
+ // Build an index of the class path that we can then
+ // use destructively
+ MultiMap<String,Jar> packages = new MultiMap<String,Jar>();
+ for (Jar srce : getClasspath()) {
+ dot.updateModified(srce.lastModified, srce + " (" + srce.lastModifiedReason + ")");
+ for (Entry<String,Map<String,Resource>> e : srce.getDirectories().entrySet()) {
+ if (e.getValue() != null)
+ packages.add(e.getKey(), srce);
+ }
+ }
+
+ Parameters privatePackages = getPrivatePackage();
+ if (isTrue(getProperty(Constants.UNDERTEST))) {
+ String h = getProperty(Constants.TESTPACKAGES, "test;presence:=optional");
+ privatePackages.putAll(parseHeader(h));
+ }
+
+ if (!privatePackages.isEmpty()) {
+ Instructions privateFilter = new Instructions(privatePackages);
+ Set<Instruction> unused = doExpand(dot, packages, privateFilter);
+
+ if (!unused.isEmpty()) {
+ warning("Unused " + Constants.PRIVATE_PACKAGE + " instructions, no such package(s) on the class path: %s", unused);
+ }
+ }
+
+ Parameters exportedPackage = getExportPackage();
+ if (!exportedPackage.isEmpty()) {
+ Instructions exportedFilter = new Instructions(exportedPackage);
+
+ // We ignore unused instructions for exports, they should show
+ // up as errors during analysis. Otherwise any overlapping
+ // packages with the private packages should show up as
+ // unused
+
+ doExpand(dot, packages, exportedFilter);
+ }
+}
+
+
+ /**
+ * Check if the given resource is in scope of this bundle. That is, it
+ * checks if the Include-Resource includes this resource or if it is a class
+ * file it is on the class path and the Export-Package or Private-Package
+ * include this resource.
+ *
+ * @param f
+ * @return
+ */
+public boolean isInScope(Collection<File> resources) throws Exception {
+ Parameters clauses = parseHeader(getProperty(Constants.EXPORT_PACKAGE));
+ clauses.putAll(parseHeader(getProperty(Constants.PRIVATE_PACKAGE)));
+ clauses.putAll(parseHeader(getProperty(Constants.PRIVATEPACKAGE)));
+ if (isTrue(getProperty(Constants.UNDERTEST))) {
+ clauses.putAll(parseHeader(getProperty(Constants.TESTPACKAGES, "test;presence:=optional")));
+ }
+
+ Collection<String> ir = getIncludedResourcePrefixes();
+
+ Instructions instructions = new Instructions(clauses);
+
+ for (File r : resources) {
+ String cpEntry = getClasspathEntrySuffix(r);
+
+ if (cpEntry != null) {
+
+ if (cpEntry.equals("")) // Meaning we actually have a CPE
+ return true;
+
+ String pack = Descriptors.getPackage(cpEntry);
+ Instruction i = matches(instructions, pack, null, r.getName());
+ if (i != null)
+ return !i.isNegated();
+ }
+
+ // Check if this resource starts with one of the I-C header
+ // paths.
+ String path = r.getAbsolutePath();
+ for (String p : ir) {
+ if (path.startsWith(p))
+ return true;
+ }
+ }
+ return false;
+}
+
/** + * This method is about compatibility. New behavior can be conditionally + * introduced by calling this method and passing what version this behavior + * was introduced. This allows users of bnd to set the -upto instructions to + * the version that they want to be compatible with. If this instruction is + * not set, we assume the latest version. + */
+ +Version upto = null;
+
+public boolean since(Version introduced) {
+ if (upto == null) {
+ String uptov = getProperty(UPTO);
+ if (uptov == null) {
+ upto = Version.HIGHEST;
+ return true;
+ }
+ if (!Version.VERSION.matcher(uptov).matches()) {
+ error("The %s given version is not a version: %s", UPTO, uptov);
+ upto = Version.HIGHEST;
+ return true;
+ }
+
+ upto = new Version(uptov);
+ }
+ return upto.compareTo(introduced) >= 0;
+}
+
In OSGi Enterprise 4.2 the concept of Web Archive Bundles were introduced. Web Archive Bundles are 100% normal bundles following all the rules of OSGi. Their speciality is that they can be mapped to a web server following several of the rules of Java Enterprise Edition’s Servlet model. The big difference is that the WARs of the servlet model have a rather strict layout of their archive because the servlet container also handles class loading. In OSGi, the class loading is very well specified and it would therefore be wrong to create special rules.
+ +However, the OSGi supports the Bundle-Classpath header. This header allows the organization of the internal layout. It turns out that it is possible to create a valid Web Application Bundle (WAB) that is also a valid Web ARchive (WAR). Being deploy an archive both to OSGi and an application server obviously has advantages. bnd therefore supports a number of instructions that make it easy to create these dual mode archives.
+ +The -wab
instruction instructs bnd to move the root of the created archive to WEB-INF/classes. That is, you build your bundle in the normal way,not using the Bundle-ClassPath
. The -wab
command then moves the root of the archive so the complete class path for the bundle is no inside the WEB-INF/classes directory. It then adjusts the Bundle-ClassPath
header to reflect this new location of the classes and resources.
The new root now only contains WEB-INF. In the Servlet specification, the root of the archive is mapped to the server’s context URL. It is therefore often necessary to place static files in the root. For this reason, the -wab
instruction has the same form as Include-Resource header, and performs the same function. However, it performs this function of copying resources from the file system after the classes and resources of the original bundle have been moved to WEB-INF.
For example, the following code creates a simple WAB/WAR:
+ + Private-Package: com.example.impl.*
+ Export-Package: com.example.service.myapi
+ Include-Resource: resources/
+ -wab: static-pages/
+
The layout of the resulting archive is:
+ + WEB-INF/classes/com/example/impl/
+ WEB-INF/classes/com/example/service/myapi/
+ WEB-INF/classes/resources/
+ index.html // from static-pages
+
The Bundle-ClassPath
is WEB-INF/classes
.
WARs can carry a WEB-INF/lib
directory. Any archive in this directory is mapped to the class path of the WAR. The OSGi specifications do not recognize directories with archives it is therefore necessary to list these archives also on the Bundle-ClassPath
header. This is cumbersome to do by hand so the -wablib
command will take a list of paths.
Private-Package: com.example.impl.*
+ Export-Package: com.example.service.myapi
+ Include-Resource: resources/
+ -wab: static-pages/
+ -wablib: lib/a.jar, lib/b.jar
+
This results in a layout of:
+ + WEB-INF/classes/com/example/impl/
+ WEB-INF/classes/com/example/service/myapi/
+ WEB-INF/classes/resources/
+ WEB-INF/lib/
+ a.jar
+ b.jar
+ index.html ( from static-pages)
+
The Bundle-ClassPath
is now set to WEB-INF/classes,WEB-INF/lib/a.jar,WEB-INF/lib/a.jar
/**
+ * Turn this normal bundle in a web and add any resources.
+ *
+ * @throws Exception
+ */
+ private Jar doWab(Jar dot) throws Exception {
+ String wab = getProperty(WAB);
+ String wablib = getProperty(WABLIB);
+ if (wab == null && wablib == null)
+ return dot;
+
+ trace("wab %s %s", wab, wablib);
+ setBundleClasspath(append("WEB-INF/classes", getProperty(BUNDLE_CLASSPATH)));
+
+ Set<String> paths = new HashSet<String>(dot.getResources().keySet());
+
+ for (String path : paths) {
+ if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
+ trace("wab: moving: %s", path);
+ dot.rename(path, "WEB-INF/classes/" + path);
+ }
+ }
+
+ Parameters clauses = parseHeader(getProperty(WABLIB));
+ for (String key : clauses.keySet()) {
+ File f = getFile(key);
+ addWabLib(dot, f);
+ }
+ doIncludeResource(dot, wab);
+ return dot;
+ }
+
/**
+ * Turn this normal bundle in a web and add any resources.
+ *
+ * @throws Exception
+ */
+private Jar doWab(Jar dot) throws Exception {
+ String wab = getProperty(WAB);
+ String wablib = getProperty(WABLIB);
+ if (wab == null && wablib == null)
+ return dot;
+
+ trace("wab %s %s", wab, wablib);
+ setBundleClasspath(append("WEB-INF/classes", getProperty(BUNDLE_CLASSPATH)));
+
+ Set<String> paths = new HashSet<String>(dot.getResources().keySet());
+
+ for (String path : paths) {
+ if (path.indexOf('/') > 0 && !Character.isUpperCase(path.charAt(0))) {
+ trace("wab: moving: %s", path);
+ dot.rename(path, "WEB-INF/classes/" + path);
+ }
+ }
+
+ Parameters clauses = parseHeader(getProperty(WABLIB));
+ for (String key : clauses.keySet()) {
+ File f = getFile(key);
+ addWabLib(dot, f);
+ }
+ doIncludeResource(dot, wab);
+ return dot;
+}
+
A workingset is a name for a group of projects that have something in common. You can tag a project for one or more workingsets using the -workingset
instruction in the bnd.bnd
file. For example, to make a project member of the Implementations
and Drivers
working sets you can enter the following in a bnd file:
-workingset Implementations, Drivers
+
An IDE can use this information to provide a grouping to projects. However, this information can also be used to tag bundles in the Manifest, like:
+ +Bundle-Category ${-workingset.*}
+
The syntax for the instruction is:
+ +workingset := '-workingset' tag ( ',' tag )*
+tag := NAME ( ';member=' TRUTHY )?
+NAME := <JavaIdentifierPart+>
+
The instruction is a merged instruction so you can also set:
+ +-workingset.all All
+
Note that, the name of the working set must use the pattern of Java identifier.
+ +As you could see in the syntax, you can also specify a member
attribute on the tag. This member attribute evaluates to a truthy. A truthy is true
if it is not empty, 0, nor false
. The truthy is well supported by macros so you can now use the bnd macro language to decide if a project should be a member of a working set or not. This can be used in many ways. For example, you could use it to do name matching. By placing the following in the cnf/build.bnd
file you do not have to place the -workingset
instruction in each bnd file.
-workingset = \
+ impl;member=${filter;${p};.*\.provider}, \
+ api;member=${filter;${p};.*\.api}, \
+ test;member=${filter;${p};.*\.test}
+
The feature will create working sets as demanded but will reuse existing working set with the matching name. If no -workingset
instruction is given, the working sets are not touched in any way for that project. That is, they are then not removed from existing sets.
In some cases it is necessary to maintain a working set manual. Such a workingset is then stored in Eclipse and not shared with the team. To
+create a manual working set, use a name that is outside the specified NAME pattern. For example, use a name that starts with a
+dot (.
) like .Private
. Since you cannot use these names in the -workingset
instruction (they generate an error)
+bnd will never look at workingsets with such a name.
bnd is the Swiss army knife of OSGi, it is used for creating and working with OSGi bundles. Its primary goal is take the pain out of developing bundles. With OSGi you are forced to provide additional metadata in the JAR’s manifest to verify the consistency of your “class path”. This metadata must be closely aligned with the class files in the bundle and the policies that a company has about versioning. Maintaining this metdata is an error prone chore because many aspects are redundant.
+ +bnd’s raison d’etre is therefore to remove the chores and use the redundancy to create the manifest from the class files instead of maintaining it by hand. The core task is therefore to analyze the class files and find any dependencies. These dependencies are then merged with ‘‘instructions’’ supplied by the user. For example, adding a version to all imported packages from a specific library can be specified as:
+ +Import-Package: com.library.*; version = 1.21
+
The OSGi manifest must explicitly mention a package, bnd allows the use of wildcards. bnd contains many more such conveniences. bnd roots are about 10 years old and bnd has therefore a large number of functions that remove such chores. These range from simplifying the use of OSGi Declarative Services, working with Spring and Blueprint, WAR and WAB files, version analysis, project dependencies, and much more.
+ +Over time bnd started to appear in many different incarnations. It is an an ant task, a command line utility, and a bundle for Eclipse. Other projects have used bndlib to create a maven plugin, bndtools and Sigil both Eclipse IDEs, and others. By keeping the core library small and uncoupled (bnd has no external connections except Java 5), it is easy to embed the functionality in other projects.
+ +Traditionally, JAR files were made with the JDK jar tool, the jar ant task, or the Maven packager. All these tools share the same concept. The developer creates a directory image of the jar by copying files to a directory; this directory is then jarred. This model can be called the ‘‘push’’ model. Obviously this method works well.
+ +bnd works differently, it uses the ‘‘pull’’ model. Instructions in the bnd file describe the contents of the desired JAR file without writing this structure to disk. The contents from the output can come from the class path or from anywhere in the file system. For example, the following instruction includes the designated packages in the JAR:
+ +Private-Package: com.example.*
+ +bnd can create a JAR from packages the sources, directories or other JAR files. You never have to copy files around, the instructions that Bnd receives are sufficient to retrieve the files from their original location, preprocessing or filtering when required.
+ +The Jar is constructed from 3 different arguments:
+ +Export-Package
+Private-Package
+Include-Resource
+
Private-Package and Export-Package contain ‘‘instructions’’. Instructions are patterns + attributes and directives, looking like normal OSGi attributes and directives. For example:
+ +Export-Package: com.acme.*;version=1.2
+
Each instruction is applied to each package on the classpath in the definition order. That is, if an earlier instruction matches, the later instruction never gets a chance to do its work. If an instruction matches its attributes and properties are applied to the packages. The difference between the Private-Package argument and the Export-Package arguments is that the export version selects the packages for export. If the packages overlap between the two, the export wins.
+ +An instruction can also be negative when it starts with a ‘!’. In that case the package is excluded from the selection. For example:
+ +Export-Package: !com.acme.impl, com.acme.*;version=1.2
+
Note that the instructions are applied in order. If the ! instruction was at the end in the previous example, it would not have done its work because the com.acme.* would already have matched.
+ +The Include-Resource argument can be used to copy resources from the file system in the JAR. This is useful for licenses, images, etc. The instructions in the argument can be a directory, a file, or an inline JAR. The default JAR path is the the root for a directory or the filename for a file. The path can be overridden. Instructions that are enclosed in curly braces, like {license.txt}, are pre-processed, expanding any macros in the file.
+ +Once the JAR is created, the bnd program analyzes the classes and creates an import list with all the packages that are not contained in the jar but which are referred to. This import list is matched against the Import-Package instructions. Normally, the Import-Package argument is *; all referred packages will be imported. However, sometimes it is necessary to ignore an import or provide attributes on the import statement. For example, make the import optional or discard the import:
+ +Import-Package: !com.acme.*, *;resolution:=optional
+
The arguments to bnd are normal given as a set of properties. Properties that begin with an upper case are copied to the manifest (possibly after processing). Lower case properties are used for macro variables but are not set as headers in the manifest.
+ +After the JAR is created, the bnd program will verify the result. This will check the resulting manifest in painstaking detail.
+ +The bnd program works on a higher level then traditional jarring; this might take some getting used to. However, it is much more elegant to think in packages than that it is to think in files. The fact that bnd understand the semantics of a bundle allows it to detect many errors and allows bundles to be created with almost no special information.
+ +bnd will not create an output file if none of the resources is newer than an existing output file.
+ +The program is available in several forms: command line, ant task, maven plugin, and an Eclipse plugin.
+ +##Tips +There are some common pitfalls that can be prevented by following the tips:
+ +If you have a problem, make an example that is as small as possible and send it to [me][mailto:Peter.Kriens@aQute.biz | +me]. | +
The apply
macro takes the name of a macro and invokes this macro with each element of the given list as an argument. It is useful when the arguments to the macro are in a comma separated list.
args = com.example.foo, 3.12, HIGHEST
+${apply;repo;${args}}
+
This will be expanded to:
+ +${repo;com.example.foo;3.12;HIGHEST}
+
Which will provide the path to the artifact
+ +static String _average = "${average;<list>[;<list>...]}";
+
+public String _average(String args[]) throws Exception {
+ verifyCommand(args, _sum, null, 2, Integer.MAX_VALUE);
+
+ List<String> list = toList(args, 1, args.length);
+ if (list.isEmpty())
+ throw new IllegalArgumentException("No members in list to calculate average");
+
+ double d = 0;
+
+ for (String s : list) {
+ double v = Double.parseDouble(s);
+ d += v;
+ }
+ return toString(d / list.size());
+}
+
static final String _base64Help = "${base64;<file>[;fileSizeLimit]}, get the Base64 encoding of a file";
+
+/**
+ * Get the Base64 encoding of a file.
+ *
+ * @throws IOException
+ */
+public String _base64(String... args) throws IOException {
+ verifyCommand(args, _base64Help, null, 2, 3);
+
+ File file = domain.getFile(args[1]);
+ long maxLength = 100_000;
+ if (args.length > 2)
+ maxLength = Long.parseLong(args[2]);
+
+ if (file.length() > maxLength)
+ throw new IllegalArgumentException(
+ "Maximum file size (" + maxLength + ") for base64 macro exceeded for file " + file);
+
+ return Base64.encodeBase64(file);
+}
+
public String _basedir(@SuppressWarnings("unused") String args[]) {
+ if (base == null)
+ throw new IllegalArgumentException("No base dir set");
+
+ return base.getAbsolutePath();
+}
+
public String _basename(String args[]) {
+ if (args.length < 2) {
+ domain.warning("Need at least one file name for ${basename;...}");
+ return null;
+ }
+ String del = "";
+ StringBuilder sb = new StringBuilder();
+ for (int i = 1; i < args.length; i++) {
+ File f = domain.getFile(args[i]);
+ if (f.exists() && f.getParentFile().exists()) {
+ sb.append(del);
+ sb.append(f.getName());
+ del = ",";
+ }
+ }
+ return sb.toString();
+
+}
+
Returns the basename of the specified path optionally minus a specified extension. This is the last segment of the path. If an extension is specified, the basename is examined for a .
separating the extension from the rest of the file name. If the extension of the basename matches the specified extension, this extension is removed from the basename before it is returned. The extension, if specified, may optionally start with .
.
# returns 'abcdef.def'
+${basenameext;abcdef.def}
+${basenameext;/foo.bar/abcdef.def}
+${basenameext;abcdef.def;bar}
+${basenameext;/foo.bar/abcdef.def;bar}
+
+# returns 'abcdef'
+${basenameext;abcdef.def;def}
+${basenameext;/foo.bar/abcdef.def;def}
+${basenameext;abcdef.def;.def}
+${basenameext;/foo.bar/abcdef.def;.def}
+
Returns the currently running bnd version as full minor.major.micro. E.g 5.3.0
+
public String _bsn(String args[]) {
+ return getBsn();
+}
+
/**
+ * Format bytes
+ */
+public String _bytes(String[] args) {
+ try (Formatter sb = new Formatter()) {
+ for (int i = 0; i < args.length; i++) {
+ long l = Long.parseLong(args[1]);
+ bytes(sb, l, 0, new String[] {
+ "b", "Kb", "Mb", "Gb", "Tb", "Pb", "Eb", "Zb", "Yb", "Bb", "Geopbyte"
+ });
+ }
+ return sb.toString();
+ }
+}
+
+private void bytes(Formatter sb, double l, int i, String[] strings) {
+ if (l > 1024 && i < strings.length - 1) {
+ bytes(sb, l / 1024, i + 1, strings);
+ return;
+ }
+ l = Math.round(l * 10) / 10;
+ sb.format("%s %s", l, strings[i]);
+}
+
/**
+ * Get the contents of a file.
+ *
+ * @param in
+ * @return
+ * @throws IOException
+ */
+
+public String _cat(String args[]) throws IOException {
+ verifyCommand(args, "${cat;<in>}, get the content of a file", null, 2, 2);
+ File f = domain.getFile(args[1]);
+ if (f.isFile()) {
+ return IO.collect(f);
+ } else if (f.isDirectory()) {
+ return Arrays.toString(f.list());
+ } else {
+ try {
+ URL url = new URL(args[1]);
+ return IO.collect(url, "UTF-8");
+ }
+ catch (MalformedURLException mfue) {
+ // Ignore here
+ }
+ return null;
+ }
+}
+
The classes macro provides a query function in an analyzed bundle. While analyzing, the Analyzer stores each found class on the Bundle-Classpath with some key information. A simple query language is used to query this dictionary. For example, if you want to make a manifest header with all public classes in the bundle:
+ +Public-Classes: ${classes;PUBLIC}
+
The query language is conjunctive, that is, all entries form an AND. For example, if you want to find all PUBLIC classes that are also not abstract you would do:
+ +PublicConcrete-Classes: ${classes;PUBLIC;CONCRETE}
+
Some query types can also take parameters. This is a pattern that must match some aspect of the class. For example, it is possible to query for classes that extend a certain base class or is annotated by a certain annotation type:
+ +Test-Cases: ${classes;CONCRETE;EXTENDS;junit.framework.TestCase}
+Test-Cases: ${classes;CONCRETE;HIERARCHY_ANNOTATED;org.junit.Test}
+Test-Cases: ${classes;CONCRETE;HIERARCHY_INDIRECTLY_ANNOTATED;org.junit.platform.commons.annotation.Testable}
+
All pattern matching is based on fully qualified name and uses the globbing model.
+ +The following table specifies what query options there are:
+ +Query | +Parameter | +Description | +
---|---|---|
ANY | ++ | Matches any class | +
CONCRETE | ++ | Class must not be abstract. | +
ABSTRACT | ++ | Class must be abstract. | +
PUBLIC | ++ | Class must be public. | +
STATIC | ++ | Class must be explicitly or implicitly declared static. That is, the class must not be a inner class. | +
INNER | ++ | Class must be an inner class. That is, the class must be a nested class that is not explicitly or implicitly declared static. Inner classes include anonymous and local classes. | +
CLASSANNOTATIONS | ++ | The class must have some CLASS retention annotations. | +
RUNTIMEANNOTATIONS | ++ | The class must have some RUNTIME retention annotations. | +
DEFAULT_CONSTRUCTOR | ++ | The class must have a default constructor. That is, the class must have a public, no-argument constructor. | +
IMPLEMENTS | +PATTERN | +The class must implement at least one fully qualified interface name that matches the given pattern. This takes inheritance into account as long as super types can be found on the classpath. This query uses the Java source code fully qualified name where `.` (not `$`) separates the nested class name from the outer class name. | +
EXTENDS | +PATTERN | +The class must extend at least one fully qualified class name that matches the given pattern. This takes inheritance into account as long as super types can be found on the classpath. This query uses the Java source code fully qualified name where `.` (not `$`) separates the nested class name from the outer class name. | +
IMPORTS | +PATTERN | +The class must use a type from another package name that matches the given pattern | +
NAMED | +PATTERN | +The fully qualified name of the class must match the given pattern. This query uses the Java source code fully qualified name where `.` (not `$`) separates the nested class name from the outer class name. | +
VERSION | +PATTERN | +The class format of the given class must match the given version. The version is given as "major.minor", like "49.0". To select classes that are Java 6, do ${classes;VERSION;49.*} |
+
ANNOTATED | +PATTERN | +The class must be directly annotated with a fully qualified annotation name that matches the pattern. The set of annotations is all annotations in the class, also the annotations on fields and methods. This query uses the Java class name fully qualified name where `$` (not `.`) separates the nested class name from the outer class name. | +
INDIRECTLY_ANNOTATED | +PATTERN | +The class must be directly or indirectly annotated with a fully qualified annotation name that matches the pattern. The set of annotations is all annotations in the class, the annotations on fields and methods, and all the annotations on those annotations recursively. This query uses the Java class name fully qualified name where `$` (not `.`) separates the nested class name from the outer class name. | +
HIERARCHY_ANNOTATED | +PATTERN | +The class, or one of its super classes, must be directly annotated with a fully qualified annotation name that matches the pattern. The set of annotations is all annotations in the class, also the annotations on fields and methods. This query uses the Java class name fully qualified name where `$` (not `.`) separates the nested class name from the outer class name. | +
HIERARCHY_INDIRECTLY_ANNOTATED | +PATTERN | +The class, or one of its super classes, must be directly or indirectly annotated with a fully qualified annotation name that matches the pattern. The set of annotations is all annotations in the class, the annotations on fields and methods, and all the annotations on those annotations recursively. This query uses the Java class name fully qualified name where `$` (not `.`) separates the nested class name from the outer class name. | +
bnd will attempt to use the resources on the classpath if a super class or interface that is referenced from an analyzed class is not in the class space. However, bnd does not require that all dependencies are available on the classpath. In such a case it is not possible to do a complete analysis. For example, if A extends B and B extends C then it can only be determined that A extends C if B can be analyzed.
+ +Compare two strings by using the compareTo method of the String class. The result “0” means the two strings are equal, “1” means the first string is greater than the second string, “-1” means the first string is less than the second string.
+ +${compare;stringA;stringB}
+
public String _currenttime(String args[]) {
+ return Long.toString(System.currentTimeMillis());
+}
+
The decorated
macro is intended to make it very simple to decorate information based on a Parameters. Parameters
+are the bnd workhorse to store information. The macro takes the following arguments:
name The name of the macro (not the value)
+boolean Whether to add unused literals. Defaults to false.
+
Decorate means that the property with the same name but with a +
at the end will be matched with the value. In this property the
+key is a glob. It is matched against the key from the original merged properties. Any matching properties get the attributes
+from the decorator.
> parameters=a,b
+> parameters.extra=c,d
+> parameters+=(c|d);attr=X
+> ${decorated;parameters}
+a,b,c;attr=X,d;attr=X
+
+> parameters=a,b
+> parameters.extra=c,d
+> parameters+=(c|d);attr=X,x,y,z
+> ${decorated;parameters;true}
+a,b,c;attr=X,d;attr=X,x,y,z
+
See instructions for more information about decorated parameters.
+ +The specified key is looked up in properties and returned. If the property name specified +by key is not set, then the default string is returned. The default is an empty string if not +specified.
+ +static final String _digestHelp = "${digest;<algo>;<in>}, get a digest (e.g. MD5, SHA-256) of a file";
+
+/**
+ * Get a digest of a file.
+ *
+ * @throws NoSuchAlgorithmException
+ * @throws IOException
+ */
+public String _digest(String... args) throws NoSuchAlgorithmException, IOException {
+ verifyCommand(args, _digestHelp, null, 3, 3);
+
+ MessageDigest digester = MessageDigest.getInstance(args[1]);
+ File f = domain.getFile(args[2]);
+ IO.copy(f, digester);
+ byte[] digest = digester.digest();
+ return Hex.toHexString(digest);
+}
+
public String _dir(String[] args) {
+ if (args.length < 2) {
+ reporter.warning("Need at least one file name for ${dir;...}");
+ return null;
+ }
+ String result = Arrays.stream(args, 1, args.length)
+ .map(domain::getFile)
+ .filter(File::exists)
+ .map(File::getParentFile)
+ .filter(File::exists)
+ .map(IO::absolutePath)
+ .collect(Strings.joining());
+ return result;
+}
+
Added support for an environment driver of bnd. This driver should be set when bnd is started by for example gradle or ant. The driver can be overridden with the -bnd-driver property.
+ +public void testDriver() throws Exception {
+ Attrs attrs = new Attrs();
+ attrs.put("x", "10");
+
+ Workspace w = new Workspace(tmp);
+
+ assertEquals("unset", w.getDriver());
+ assertEquals( "unset", w.getReplacer().process("${driver}"));
+ assertEquals( "unset", w.getReplacer().process("${driver;unset}"));
+ assertEquals( "", w.getReplacer().process("${driver;set}"));
+
+ Workspace.setDriver("test");
+ assertEquals("test", w.getDriver());
+ assertEquals( "test", w.getReplacer().process("${driver}"));
+ assertEquals( "test", w.getReplacer().process("${driver;test}"));
+ assertEquals( "", w.getReplacer().process("${driver;nottest}"));
+
+ w.setProperty("-bnd-driver", "test2");
+ assertEquals("test2", w.getDriver());
+ assertEquals( "test2", w.getReplacer().process("${driver}"));
+ assertEquals( "test2", w.getReplacer().process("${driver;test2}"));
+ assertEquals( "", w.getReplacer().process("${driver;nottest}"));
+
+
+ w.close();
+}
+
+/**
+ * Get the bnddriver, can be null if not set. The overallDriver is the
+ * environment that runs this bnd.
+ */
+public String getDriver() {
+ if (driver == null) {
+ driver = getProperty(Constants.BNDDRIVER, null);
+ if ( driver != null)
+ driver = driver.trim();
+ }
+
+ if (driver != null)
+ return driver;
+
+ return overallDriver;
+}
+
+/**
+ * Set the driver of this environment
+ */
+public static void setDriver(String driver) {
+ overallDriver = driver;
+}
+
+/**
+ * Macro to return the driver. Without any arguments, we return the name of
+ * the driver. If there are arguments, we check each of the arguments
+ * against the name of the driver. If it matches, we return the driver name.
+ * If none of the args match the driver name we return an empty string
+ * (which is false).
+ */
+
+public String _driver(String args[]) {
+ if (args.length == 1) {
+ return getDriver();
+ }
+ String driver = getDriver();
+ if (driver == null)
+ driver = getProperty(Constants.BNDDRIVER);
+
+ if (driver != null) {
+ for (int i = 1; i < args.length; i++) {
+ if (args[i].equalsIgnoreCase(driver))
+ return driver;
+ }
+ }
+ return "";
+}
+
+ public void testGestalt() throws Exception {
+ Attrs attrs = new Attrs();
+ attrs.put("x", "10");
+ Workspace.addGestalt("peter", attrs);
+ Workspace w = new Workspace(tmp);
+
+ assertEquals( "peter", w.getReplacer().process("${gestalt;peter}"));
+ assertEquals( "10", w.getReplacer().process("${gestalt;peter;x}"));
+ assertEquals( "10", w.getReplacer().process("${gestalt;peter;x;10}"));
+ assertEquals( "", w.getReplacer().process("${gestalt;peter;x;11}"));
+ assertEquals( "", w.getReplacer().process("${gestalt;peter;y}"));
+ assertEquals( "", w.getReplacer().process("${gestalt;john}"));
+ assertEquals( "", w.getReplacer().process("${gestalt;john;x}"));
+ assertEquals( "", w.getReplacer().process("${gestalt;john;x;10}"));
+
+ w.close();
+ w = new Workspace(tmp);
+ w.setProperty("-gestalt", "john;z=100, mieke;a=1000, ci");
+ assertEquals( "peter", w.getReplacer().process("${gestalt;peter}"));
+ assertEquals( "10", w.getReplacer().process("${gestalt;peter;x}"));
+ assertEquals( "10", w.getReplacer().process("${gestalt;peter;x;10}"));
+ assertEquals( "", w.getReplacer().process("${gestalt;peter;x;11}"));
+ assertEquals( "", w.getReplacer().process("${gestalt;peter;y}"));
+ assertEquals( "john", w.getReplacer().process("${gestalt;john}"));
+ assertEquals( "100", w.getReplacer().process("${gestalt;john;z}"));
+ assertEquals( "100", w.getReplacer().process("${gestalt;john;z;100}"));
+ assertEquals( "", w.getReplacer().process("${gestalt;john;z;101}"));
+ assertEquals( "mieke", w.getReplacer().process("${gestalt;mieke}"));
+ assertEquals( "", w.getReplacer().process("${gestalt;mieke;x}"));
+
+ w.close();
+}
+
public String _ee(String args[]) {
+ return getHighestEE().getEE();
+}
+
static String _endswith = "${endswith;<string>;<suffix>}";
+
+public String _endswith(String args[]) throws Exception {
+ verifyCommand(args, _endswith, null, 3, 3);
+ if (args[1].endsWith(args[2]))
+ return args[1];
+ else
+ return "";
+}
+
The specified key is looked up in System.env
and returned. If the environment variable specified
+by key is not set, then the default string is returned. The default is an empty string if not
+specified.
public String _error(String args[]) {
+ for (int i = 1; i < args.length; i++) {
+ domain.error(process(args[i]));
+ }
+ return "";
+}
+
public String _exporters(String args[]) throws Exception {
+ Macro.verifyCommand(args, "${exporters;<packagename>}, returns the list of jars that export the given package",
+ null, 2, 2);
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+ String pack = args[1].replace('.', '/');
+ for (Jar jar : classpath) {
+ if (jar.getDirectories().containsKey(pack)) {
+ sb.append(del);
+ sb.append(jar.getName());
+ }
+ }
+ return sb.toString();
+}
+
public String _exports(String[] args) {
+ return join(filter(getExports().keySet(), args));
+}
+
Returns the file extension of the specified path. The last segment of the path is examined for a .
separating the extension from the rest of the file name. The extension is returned or, if the file name has no extension, an empty string is returned.
# returns 'def'
+${extension;abcdef.def}
+${extension;/foo.bar/abcdefxyz.def}
+
+# Returns empty string
+${extension;abcdefxyz}
+${extension;/foo.bar/abcdefxyz}
+
The specified path is evaluated against the base path of the domain Processor +and the file uri is returned.
+ +So ${fileuri;.}
will return the file uri of a project base if used in a project’s bnd file,
+for example bnd.bnd
, or the file uri of the workspace base if used in a workspace’s bnd
+file, for example cnf/build.bnd
.
public String _filter(String args[]) {
+ return filter(args, false);
+}
+
+public String _filterout(String args[]) {
+ return filter(args, true);
+
+}
+
+static String _filterHelp = "${ % s;<list>;<regex>}";
+
+String filter(String[] args, boolean include) {
+ verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);
+
+ Collection<String> list = new ArrayList<String>(Processor.split(args[1]));
+ Pattern pattern = Pattern.compile(args[2]);
+
+ for (Iterator<String> i = list.iterator(); i.hasNext();) {
+ if (pattern.matcher(i.next()).matches() == include)
+ i.remove();
+ }
+ return Processor.join(list);
+}
+
public String _filter(String args[]) {
+ return filter(args, false);
+}
+
+public String _filterout(String args[]) {
+ return filter(args, true);
+
+}
+
+static String _filterHelp = "${ % s;<list>;<regex>}";
+
+String filter(String[] args, boolean include) {
+ verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);
+
+ Collection<String> list = new ArrayList<String>(Processor.split(args[1]));
+ Pattern pattern = Pattern.compile(args[2]);
+
+ for (Iterator<String> i = list.iterator(); i.hasNext();) {
+ if (pattern.matcher(i.next()).matches() == include)
+ i.remove();
+ }
+ return Processor.join(list);
+}
+
static String _find = "${find;<target>;<searched>}";
+
+public int _find(String args[]) throws Exception {
+ verifyCommand(args, _find, null, 3, 3);
+
+ return args[1].indexOf(args[2]);
+}
+
public String _findfile(String args[]) {
+ File f = getFile(args[1]);
+ List<String> files = new ArrayList<String>();
+ tree(files, f, "", new Instruction(args[2]));
+ return join(files);
+}
+
static String _findlast = "${findlast;<find>;<target>}";
+
+public int _findlast(String args[]) throws Exception {
+ verifyCommand(args, _findlast, null, 3, 3);
+
+ return args[2].lastIndexOf(args[1]);
+}
+
public String _findname(String args[]) {
+ return findPath("findname", args, false);
+}
+
+String findPath(String name, String[] args, boolean fullPathName) {
+ if (args.length > 3) {
+ warning("Invalid nr of arguments to " + name + " " + Arrays.asList(args) + ", syntax: ${" + name
+ + " (; reg-expr (; replacement)? )? }");
+ return null;
+ }
+
+ String regexp = ".*";
+ String replace = null;
+
+ switch (args.length) {
+ case 3 :
+ replace = args[2];
+ //$FALL-THROUGH$
+ case 2 :
+ regexp = args[1];
+ }
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+
+ Pattern expr = Pattern.compile(regexp);
+ for (Iterator<String> e = dot.getResources().keySet().iterator(); e.hasNext();) {
+ String path = e.next();
+ if (!fullPathName) {
+ int n = path.lastIndexOf('/');
+ if (n >= 0) {
+ path = path.substring(n + 1);
+ }
+ }
+
+ Matcher m = expr.matcher(path);
+ if (m.matches()) {
+ if (replace != null)
+ path = m.replaceAll(replace);
+
+ sb.append(del);
+ sb.append(path);
+ del = ", ";
+ }
+ }
+ return sb.toString();
+}
+
public String _findname(String args[]) {
+ return findPath("findname", args, false);
+}
+
+String findPath(String name, String[] args, boolean fullPathName) {
+ if (args.length > 3) {
+ warning("Invalid nr of arguments to " + name + " " + Arrays.asList(args) + ", syntax: ${" + name
+ + " (; reg-expr (; replacement)? )? }");
+ return null;
+ }
+
+ String regexp = ".*";
+ String replace = null;
+
+ switch (args.length) {
+ case 3 :
+ replace = args[2];
+ //$FALL-THROUGH$
+ case 2 :
+ regexp = args[1];
+ }
+ StringBuilder sb = new StringBuilder();
+ String del = "";
+
+ Pattern expr = Pattern.compile(regexp);
+ for (Iterator<String> e = dot.getResources().keySet().iterator(); e.hasNext();) {
+ String path = e.next();
+ if (!fullPathName) {
+ int n = path.lastIndexOf('/');
+ if (n >= 0) {
+ path = path.substring(n + 1);
+ }
+ }
+
+ Matcher m = expr.matcher(path);
+ if (m.matches()) {
+ if (replace != null)
+ path = m.replaceAll(replace);
+
+ sb.append(del);
+ sb.append(path);
+ del = ", ";
+ }
+ }
+ return sb.toString();
+}
+
The findproviders
macro gives access to the resources in the Workspace repositories, including the projects itself. Its semantics match the OSGi Repository.findProviders()
method.
It was added to support the use case of including a set of plugins in a resolve to create an executable. Since it is impossible to add a requirement that will add all matching resources, the findproviders
macro can be used to find these resources and then turn them into initial requirements. However, this macro will likely find many other use cases since it is quite versatile. If used outside of bndrun
files, it can cause circular builds because it might want to search the project that is used in. Use the REPOS Strategy in this case.
findproviders ::= namespace ( ';' FILTER ( ';' STRATEGY)? )?
+namespace ::= ... OSGi namespace
+FILTER ::= Any LDAP Styled Filter
+STRATEGY ::= 'ALL' | 'WORKSPACE' | 'REPOS'
+
If no filter is given or the filter attribute is empty, then all resources that have a capability in the given namespace are returned.
+ +The following strategies are available:
+ +Strategy | +Description | +
---|---|
ALL | +Searches in the Workspace and all configured Repositories | +
REPOS | +Searches only in configured Repositories | +
WORKSPACE | +Searches in Workspace projects only | +
The result is in PARAMETERS
format, to common format in bnd and OSGi. In this case the key will be the Bundle Symbolic Name and the attributes will at least contain the Bundle version. For example:
${findproviders;osgi.service}
+
This finds all resources that have an osgi.service
capability. This could return something like:
com.h2database;version=1.4.198,
+org.apache.aries.async;version=1.0.1,
+org.apache.felix.configadmin;version=1.9.12,
+org.apache.felix.coordinator;version=1.0.2,
+org.apache.felix.eventadmin;version=1.5.0,
+...
+
This PARAMETERS
format is structured because it has attributes; that makes it harder to use with all kind of other macros. The template
macro was intended to help out. This macro takes the name of macro and then treats the contents as a PARAMETERS
. It then applies a template to each clause in the PARAMETERS. For example:
my.plugins = ${findproviders;osgi.service;\
+ (objectClass=com.example.my.Plugin)}
+-runrequires.plugins = ${template;my.plugins;\
+ osgi.identity;filter:='(osgi.identity=${@})'}
+
This would turn the previous example into:
+ +-runrequires.plugins = \
+ osgi.identity;filter:='(osgi.identity=com.h2database)',
+ osgi.identity;filter:='(osgi.identity=org.apache.aries.async)',
+ ..
+
The usage of the findproviders
macro as depicted above also works when used in the context of one of the Maven plugins.
static String _first = "${first;<list>[;<list>...]}";
+
+public String _first(String args[]) throws Exception {
+ verifyCommand(args, _first, null, 1, Integer.MAX_VALUE);
+
+ List<String> list = toList(args, 1, args.length);
+ if (list.isEmpty())
+ return "";
+
+ return list.get(0);
+}
+
public final static String _fmodifiedHelp = "${fmodified;<list of filenames>...}, return latest modification date";
+
+public String _fmodified(String args[]) throws Exception {
+ verifyCommand(args, _fmodifiedHelp, null, 2, Integer.MAX_VALUE);
+
+ long time = 0;
+ Collection<String> names = new ArrayList<String>();
+ for (int i = 1; i < args.length; i++) {
+ Processor.split(args[i], names);
+ }
+ for (String name : names) {
+ File f = new File(name);
+ if (f.exists() && f.lastModified() > time)
+ time = f.lastModified();
+ }
+ return "" + time;
+}
+
/**
+ * Map a value from a list to a new value, providing the value and the index
+ */
+
+static String _foreach = "${foreach;<macro>[;<list>...]}";
+public String _foreach(String args[]) throws Exception {
+ verifyCommand(args, _foreach, null, 2, Integer.MAX_VALUE);
+ String macro = args[1];
+ List<String> list = toList(args,2, args.length);
+ List<String> result = new ArrayList<String>();
+
+ int n = 0;
+ for ( String s : list) {
+ String invoc = process("${" + macro +";" + s +";" + n++ +"}");
+ result.add(invoc);
+ }
+
+ return Processor.join(result);
+}
+
The format
macro can be used to format a string using the java.util.Formatter
.
+The Locale.ROOT
is used for build reproducibility.
+The arguments after the format string are converted from String to an appropriate format useful by the conversion in the format string.
/**
+ * Return
+ *
+ * <pre>
+ * ${frange;1.2.3} -> (&(version>=1.2.3)(!(version>=1.3.0))
+ * ${frange;1.2.3, true} -> (&(version>=1.2.3)(!(version>=2.0.0))
+ * </pre>
+ */
+public String _frange(String[] args) {
+ if (args.length < 2 || args.length > 3) {
+ error("Invalid filter range, 2 or 3 args ${frange;<version>[;true|false]}");
+ return null;
+ }
+
+ String v = args[1];
+ if (!Verifier.isVersion(v)) {
+ error("Invalid version arg %s", v);
+ return null;
+ }
+
+ boolean isProvider = false;
+ if (args.length == 3)
+ isProvider = Processor.isTrue(args[2]);
+
+ Version low = new Version(v);
+ Version high;
+ if (isProvider)
+ high = new Version(low.getMajor(), low.getMinor() + 1, 0);
+ else
+ high = new Version(low.getMajor() + 1, 0, 0);
+
+ StringBuilder sb = new StringBuilder("(&(version>=").append(low.getWithoutQualifier()).append(")");
+ sb.append("(!(version>=").append(high.getWithoutQualifier()).append(")))");
+
+ return sb.toString();
+}
+
/**
+ * Add a gestalt to all workspaces. The gestalt is a set of parts describing
+ * the environment. Each part has a name and optionally attributes. This
+ * method adds a gestalt to the VM. Per workspace it is possible to augment
+ * this.
+ */
+
+public static void addGestalt(String part, Attrs attrs) {
+ Attrs already = overallGestalt.get(part);
+ if (attrs == null)
+ attrs = new Attrs();
+
+ if (already != null) {
+ already.putAll(attrs);
+ } else
+ already = attrs;
+
+ overallGestalt.put(part, already);
+}
+
+/**
+ * Get the attrs for a gestalt part
+ */
+public Attrs getGestalt(String part) {
+ if (gestalt == null) {
+ gestalt = new Parameters(getProperty(Constants.GESTALT));
+ gestalt.mergeWith(overallGestalt, false);
+ }
+ return gestalt.get(part);
+}
+
+/**
+ * The macro to access the gestalt
+ * <p>
+ * {@code $ gestalt;part[;key[;value]]}}
+ */
+
+public String _gestalt(String args[]) {
+ if (args.length >= 2) {
+ Attrs attrs = getGestalt(args[1]);
+ if (attrs == null)
+ return "";
+
+ if (args.length == 2)
+ return args[1];
+
+ String s = attrs.get(args[2]);
+ if (args.length == 3) {
+ if (s == null)
+ s = "";
+ return s;
+ }
+
+ if (args.length == 4) {
+ if (args[3].equals(s))
+ return s;
+ else
+ return "";
+ }
+ }
+ throw new IllegalArgumentException("${gestalt;<part>[;key[;<value>]]} has too many arguments");
+}
+
static String _get = "${get;<index>;<list>}";
+
+public String _get(String args[]) throws Exception {
+ verifyCommand(args, _get, null, 3, 3);
+
+ int index = Integer.parseInt(args[1]);
+ List<String> list = toList(args, 2, 3);
+ if (index < 0)
+ index = list.size() + index;
+ return list.get(index);
+}
+
/**
+ * #388 Manifest header to get GIT head Get the head commit number. Look
+ * for a .git/HEAD file, going up in the file hierarchy. Then get this file,
+ * and resolve any symbolic reference.
+ *
+ * @throws IOException
+ */
+static Pattern GITREF = Pattern.compile("ref:\\s*(refs/(heads|tags|remotes)/([^\\s]+))\\s*");
+
+static String _githeadHelp = "${githead}, provide the SHA for the current git head";
+
+public String _githead(String[] args) throws IOException {
+ Macro.verifyCommand(args, _githeadHelp, null, 1, 1);
+
+ //
+ // Locate the .git directory
+ //
+
+ File rover = getBase();
+ while (rover !=null && rover.isDirectory()) {
+ File headFile = IO.getFile(rover, ".git/HEAD");
+ if (headFile.isFile()) {
+ //
+ // The head is either a symref (ref: refs/(heads|tags|remotes)/<name>)
+ //
+ String head = IO.collect(headFile).trim();
+ if (!Hex.isHex(head)) {
+ //
+ // Should be a symref
+ //
+ Matcher m = GITREF.matcher(head);
+ if (m.matches()) {
+
+ // so the commit is in the following path
+
+ head = IO.collect(IO.getFile(rover, ".git/" + m.group(1)));
+ }
+ else {
+ error("Git repo seems corrupt. It exists, find the HEAD but the content is neither hex nor a sym-ref: %s",
+ head);
+ }
+ }
+ return head.trim().toUpperCase();
+ }
+ rover = rover.getParentFile();
+ }
+ // Cannot find git directory
+ return "";
+}
+
static final String _globHelp = "${glob;<globexp>} (turn it into a regular expression)";
+
+public String _glob(String[] args) {
+ verifyCommand(args, _globHelp, null, 2, 2);
+ String glob = args[1];
+ boolean negate = false;
+ if (glob.startsWith("!")) {
+ glob = glob.substring(1);
+ negate = true;
+ }
+
+ Pattern pattern = Glob.toPattern(glob);
+ if (negate)
+ return "(?!" + pattern.pattern() + ")";
+ else
+ return pattern.pattern();
+}
+
static final String _globalHelp = "${global;<name>[;<default>]}, get a global setting from ~/.bnd/settings.json";
+
+/**
+ * Provide access to the global settings of this machine.
+ *
+ * @throws Exception
+ */
+
+public String _global(String[] args) throws Exception {
+ Macro.verifyCommand(args, _globalHelp, null, 2, 3);
+
+ String key = args[1];
+ if (key.equals("key.public"))
+ return Hex.toHexString(settings.getPublicKey());
+ if (key.equals("key.private"))
+ return Hex.toHexString(settings.getPrivateKey());
+
+ String s = settings.get(key);
+ if (s != null)
+ return s;
+
+ if (args.length == 3)
+ return args[2];
+
+ return null;
+}
+
public String _ide(String[] args) throws IOException {
+ if (args.length < 2) {
+ error("The ${ide;<>} macro needs an argument");
+ return null;
+ }
+ if (ide == null) {
+ ide = new Properties();
+ File file = getFile(".settings/org.eclipse.jdt.core.prefs");
+ if (!file.isFile()) {
+ error("The ${ide;<>} macro requires a .settings/org.eclipse.jdt.core.prefs file in the project");
+ return null;
+ }
+ FileInputStream in = new FileInputStream(file);
+ ide.load(in);
+ }
+
+ String deflt = args.length > 2 ? args[2] : null;
+ if ("javac.target".equals(args[1])) {
+ return ide.getProperty("org.eclipse.jdt.core.compiler.codegen.targetPlatform", deflt);
+ }
+ if ("javac.source".equals(args[1])) {
+ return ide.getProperty("org.eclipse.jdt.core.compiler.source", deflt);
+ }
+ return null;
+}
+
if ::= 'if' ';' condition ';' expansion ( ';' expansion )?
+condition ::= true | false
+false ::= 'false' | ''
+true ::= ! false
+expansion ::= ...
+
The ${if}
macro allows a conditional expansion. The first argument is the condition. the condition is either false (empty or ‘false’) or otherwise true. If the condition is true, the value of the macro is the second argument, the first expansion. Otherwise, if a third argument is specified this is returned. If no third argument is specified, an empty string is returned.
Notice that if a macro is not set, it will revert to its literal value. This can be confusing. For example, if the macro foo
is not set, the ${if;${foo};TRUE;FALSE}
will be TRUE
. If you want to handle unset as false, the def might be of use.
# expands to 'B'
+aorb = ${if;;A;B}
+
+# Display ${foo} if set to a non-empty string that is not false, otherwise 'Ouch'. See also ${def}
+whatisfoo = ${if;${foo};${foo};Ouch}
+
+# Include a file conditionally. If ${test} is not empty or false, the file is included
+-include ${if;${test};test.bnd}
+
public String _imports(String[] args) {
+ return join(filter(getImports().keySet(), args));
+}
+
static String _indexof = "${indexof;<value>;<list>[;<list>...]}";
+
+public int _indexof(String args[]) throws Exception {
+ verifyCommand(args, _indexof, null, 3, Integer.MAX_VALUE);
+
+ String value = args[1];
+ ExtList<String> list = toList(args, 2, args.length);
+ return list.indexOf(value);
+}
+
static String _is = "${is;<a>;<b>}";
+
+public boolean _is(String args[]) throws Exception {
+ verifyCommand(args, _is, null, 3, Integer.MAX_VALUE);
+ String a = args[1];
+
+ for (int i = 2; i < args.length; i++)
+ if (!a.equals(args[i]))
+ return false;
+
+ return true;
+}
+
public String _isdir(String args[]) {
+ boolean isdir = true;
+ // If no dirs provided, return false
+ if (args.length < 2) {
+ isdir = false;
+ }
+ for (int i = 1; i < args.length; i++) {
+ File f = new File(args[i]).getAbsoluteFile();
+ isdir &= f.isDirectory();
+ }
+ return isdir ? "true" : "false";
+
+}
+
static String _isempty = "${isempty;[<target>...]}";
+
+public boolean _isempty(String args[]) throws Exception {
+ verifyCommand(args, _isempty, null, 1, Integer.MAX_VALUE);
+
+ for (int i = 1; i < args.length; i++)
+ if (!args[i].isEmpty())
+ return false;
+
+ return true;
+}
+
public String _isfile(String args[]) {
+ if (args.length < 2) {
+ domain.warning("Need at least one file name for ${isfile;...}");
+ return null;
+ }
+ boolean isfile = true;
+ for (int i = 1; i < args.length; i++) {
+ File f = new File(args[i]).getAbsoluteFile();
+ isfile &= f.isFile();
+ }
+ return isfile ? "true" : "false";
+
+}
+
static String _isnumber = "${isnumber[;<target>...]}";
+
+public boolean _isnumber(String args[]) throws Exception {
+ verifyCommand(args, _isnumber, null, 2, Integer.MAX_VALUE);
+
+ for (int i = 1; i < args.length; i++)
+ if (!NUMERIC_P.matcher(args[i]).matches())
+ return false;
+
+ return true;
+}
+
static String _joinHelp = "${join;<list>...}";
+
+public String _join(String args[]) {
+
+ verifyCommand(args, _joinHelp, null, 1, Integer.MAX_VALUE);
+
+ List<String> result = new ArrayList<String>();
+ for (int i = 1; i < args.length; i++) {
+ Processor.split(args[i], result);
+ }
+ return Processor.join(result);
+}
+
ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
+ScriptContext context = null;
+Bindings bindings = null;
+StringWriter stdout = new StringWriter();
+StringWriter stderr = new StringWriter();
+
+static String _js = "${js [;<js expr>...]}";
+
+public Object _js(String args[]) throws Exception {
+ verifyCommand(args, _js, null, 2, Integer.MAX_VALUE);
+
+ StringBuilder sb = new StringBuilder();
+
+ for (int i = 1; i < args.length; i++)
+ sb.append(args[i]).append(';');
+
+ if (context == null) {
+ context = engine.getContext();
+ bindings = context.getBindings(ScriptContext.ENGINE_SCOPE);
+ bindings.put("domain", domain);
+ String javascript = domain.mergeProperties("javascript", ";");
+ if (javascript != null && javascript.length() > 0) {
+ engine.eval(javascript, context);
+ }
+ context.setErrorWriter(stderr);
+ context.setWriter(stdout);
+ }
+ Object eval = engine.eval(sb.toString(), context);
+ StringBuffer buffer = stdout.getBuffer();
+ if (buffer.length() > 0) {
+ domain.error("Executing js: %s: %s", sb, buffer);
+ buffer.setLength(0);
+ }
+
+ if (eval != null) {
+ return toString(eval);
+ }
+
+ String out = stdout.toString();
+ stdout.getBuffer().setLength(0);
+ return out;
+}
+
static String _last = "${last;<list>[;<list>...]}";
+
+public String _last(String args[]) throws Exception {
+ verifyCommand(args, _last, null, 1, Integer.MAX_VALUE);
+
+ List<String> list = toList(args, 1, args.length);
+ if (list.isEmpty())
+ return "";
+
+ return list.get(list.size() - 1);
+}
+
static String _lastindexof = "${lastindexof;<value>;<list>[;<list>...]}";
+
+public int _lastindexof(String args[]) throws Exception {
+ verifyCommand(args, _indexof, null, 3, Integer.MAX_VALUE);
+
+ String value = args[1];
+ ExtList<String> list = toList(args, 1, args.length);
+ return list.lastIndexOf(value);
+}
+
static String _length = "${length;<string>}";
+
+public int _length(String args[]) throws Exception {
+ verifyCommand(args, _length, null, 1, 2);
+ if (args.length == 1)
+ return 0;
+
+ return args[1].length();
+}
+
The specified keys is looked up in properties and their values returned in a list. Any unescaped semicolons are escaped with a backslash. It is useful when the arguments to a macro are a list whose elements may contain semicolons and you need to manipulate the list.
+ +deps: com.foo;version="[1,2)", com.bar;version="[1.2,2)"
+-buildpath: ${replacelist;${list;deps};$;\\;strategy=highest}
+
Results in -buildpath
having the value
com.foo;version="[1,2)";strategy=highest,com.bar;version="[1.2,2)";strategy=highest
+
public String _literal(String args[]) {
+ if (args.length != 2)
+ throw new RuntimeException("Need a value for the ${literal;<value>} macro");
+ return "${" + args[1] + "}";
+}
+
public String _long2date(String args[]) {
+ try {
+ return new Date(Long.parseLong(args[1])).toString();
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ return "not a valid long";
+}
+
The lsa
macro returns a list of absolute paths for the files in the given directory. The optional selectors can be used to filter the result.
lsa ; <dir> [ ; <selector> ] *
+
The lsr
macro returns a list of file names in the given directory. The optional selectors can be used to filter the result.
lsr ; <dir> [ ; <selector> ] *
+
/**
+ * Map a value from a list to a new value
+ */
+
+static String _map = "${map;<macro>[;<list>...]}";
+public String _map(String args[]) throws Exception {
+ verifyCommand(args, _map, null, 2, Integer.MAX_VALUE);
+ String macro = args[1];
+ List<String> list = toList(args,2, args.length);
+ List<String> result = new ArrayList<String>();
+
+ for ( String s : list) {
+ String invoc = process("${" + macro +";" + s +"}");
+ result.add(invoc);
+ }
+
+ return Processor.join(result);
+}
+
static String _matches = "${matches;<target>;<regex>}";
+
+public boolean _matches(String args[]) throws Exception {
+ verifyCommand(args, _matches, null, 3, 3);
+
+ return args[1].matches(args[2]);
+}
+
The ${version_cleanup}
macro should be used in place of this macro since it can be used in more places.
All the strings in the lists are compared, using the String.compareTo method, and the maximum value is returned.
+ +/**
+ * md5 macro
+ */
+
+static String _md5Help = "${md5;path}";
+
+public String _md5(String args[]) throws Exception {
+ Macro.verifyCommand(args, _md5Help, new Pattern[] {
+ null, null, Pattern.compile("base64|hex")
+ }, 2, 3);
+
+ Digester<MD5> digester = MD5.getDigester();
+ Resource r = dot.getResource(args[1]);
+ if (r == null)
+ throw new FileNotFoundException("From " + digester + ", not found " + args[1]);
+
+ IO.copy(r.openInputStream(), digester);
+ boolean hex = args.length > 2 && args[2].equals("hex");
+ if (hex)
+ return Hex.toHexString(digester.digest().digest());
+
+ return Base64.encodeBase64(digester.digest().digest());
+}
+
All the strings in the lists are compared, using the String.compareTo, and the minimum value is returned.
+ +/**
+ * <p>
+ * Generates a Capability string, in the format specified by the OSGi
+ * Provide-Capability header, representing the current native platform
+ * according to OSGi RFC 188. For example on Windows7 running on an x86_64
+ * processor it should generate the following:
+ * </p>
+ *
+ * <pre>
+ * osgi.native;osgi.native.osname:List<String>="Windows7,Windows 7,Win32";osgi.native.osversion:Version=6.1.0;osgi.native.processor:List<String>="x86-64,amd64,em64t,x86_64"
+ * </pre>
+ *
+ * @param args
+ * The array of properties. For example: the macro invocation of
+ * "${native_capability;osversion=3.2.4;osname=Linux}" results in
+ * an args array of
+ * [native_capability, osversion=3.2.4, osname=Linux]
+ */
+
+public String _native_capability(String[] args) throws IllegalArgumentException {
+ StringBuilder builder = new StringBuilder().append(OSGI_NATIVE);
+
+ String processorNames = null;
+ OSInformation osInformation = null;
+ IllegalArgumentException osInformationException = null;
+ /*
+ * Determine the processor information
+ */
+ String[] aliases = OSInformation.getProcessorAliases(System.getProperty("os.arch"));
+ if (aliases != null)
+ processorNames = Strings.join(aliases);
+
+ /*
+ * Determine the OS information
+ */
+
+ try {
+ osInformation = new OSInformation();
+ }
+ catch (IllegalArgumentException e) {
+ osInformationException = e;
+ }
+
+ /*
+ * Determine overrides
+ */
+
+ String osnameOverride = null;
+ Version osversionOverride = null;
+ String processorNamesOverride = null;
+
+ if (args.length > 1) {
+ assert ("native_capability".equals(args[0]));
+ for (int i = 1; i < args.length; i++) {
+ String arg = args[i];
+ String[] fields = arg.split("=", 2);
+ if (fields.length != 2) {
+ throw new IllegalArgumentException("Illegal property syntax in \"" + arg + "\", use \"key=value\"");
+ }
+ String key = fields[0];
+ String value = fields[1];
+ if (OS_NAME.equals(key)) {
+ osnameOverride = value;
+ } else if (OS_VERSION.equals(key)) {
+ osversionOverride = new Version(value);
+ } else if (OS_PROCESSOR.equals(key)) {
+ processorNamesOverride = value;
+ } else {
+ throw new IllegalArgumentException("Unrecognised/unsupported property. Supported: " + OS_NAME
+ + ", " + OS_VERSION + ", " + OS_PROCESSOR + ".");
+ }
+ }
+ }
+
+ /*
+ * Determine effective values: put determined value into override if
+ * there is no override
+ */
+
+ if (osnameOverride == null && osInformation != null) {
+ osnameOverride = osInformation.osnames;
+ }
+ if (osversionOverride == null && osInformation != null) {
+ osversionOverride = osInformation.osversion;
+ }
+ if (processorNamesOverride == null && processorNames != null) {
+ processorNamesOverride = processorNames;
+ }
+
+ /*
+ * Construct result string
+ */
+
+ builder.append(";" + OSGI_NATIVE + "." + OS_NAME + ":List<String>=\"").append(osnameOverride).append('"');
+ builder.append(";" + OSGI_NATIVE + "." + OS_VERSION + ":Version=").append(osversionOverride);
+ builder.append(";" + OSGI_NATIVE + "." + OS_PROCESSOR + ":List<String>=\"").append(processorNamesOverride)
+ .append('"');
+
+ /*
+ * Report error if needed
+ */
+
+ if (osnameOverride == null || osversionOverride == null || processorNamesOverride == null) {
+ throw new IllegalArgumentException(
+ "At least one of the required parameters could not be detected; specify an override. Detected: "
+ + builder.toString(), osInformationException);
+ }
+
+ return builder.toString();
+}
+
Compare two numbers using the Double.compare method. The result “0” means the two numbers are equal, “1” means the first number is greater than the second number, “-1” means the first number is less than the second number.
+ +${ncompare;numberA;numberB}
+
All the numbers in the lists are compared, using the Double.compare method, and the maximum number is returned.
+ +All the numbers in the lists are compared, using the Double.compare method, and the minimum number is returned.
+ +public final static String _nowHelp = "${now;pattern|'long'}, returns current time";
+
+public Object _now(String args[]) {
+ verifyCommand(args, _nowHelp, null, 1, 2);
+ Date now = new Date();
+
+ if (args.length == 2) {
+ if ("long".equals(args[1]))
+ return now.getTime();
+
+ DateFormat df = new SimpleDateFormat(args[1]);
+ return df.format(now);
+ }
+ return now;
+}
+
static String _nsortHelp = "${nsort;<list>...}";
+
+public String _nsort(String args[]) {
+ verifyCommand(args, _nsortHelp, null, 2, Integer.MAX_VALUE);
+
+ ExtList<String> result = new ExtList<String>();
+ for (int i = 1; i < args.length; i++) {
+ result.addAll(ExtList.from(args[i]));
+ }
+ Collections.sort(result, new Comparator<String>() {
+
+ public int compare(String a, String b) {
+ while (a.startsWith("0"))
+ a = a.substring(1);
+
+ while (b.startsWith("0"))
+ b = b.substring(1);
+
+ if (a.length() == b.length())
+ return a.compareTo(b);
+ else if (a.length() > b.length())
+ return 1;
+ else
+ return -1;
+
+ }
+ });
+ return result.join();
+}
+
public final static String _fileHelp = "${file;<base>;<paths>...}, create correct OS dependent path";
+
+public String _osfile(String args[]) {
+ verifyCommand(args, _fileHelp, null, 3, 3);
+ File base = new File(args[1]);
+ File f = Processor.getFile(base, args[2]);
+ return f.getAbsolutePath();
+}
+
public String _p_allsourcepath(String args[]) throws Exception {
+ return list(args, getAllsourcepath());
+}
+
public String _p_bootclasspath(String args[]) throws Exception {
+ return list(args, getBootclasspath());
+}
+
public String _p_buildpath(String args[]) throws Exception {
+ return list(args, getBuildpath());
+}
+
public String _p_dependson(String args[]) throws Exception {
+ return list(args, toFiles(getDependson()));
+}
+
public String _p_output(String args[]) throws Exception {
+ if (args.length != 1)
+ throw new IllegalArgumentException("${output} should not have arguments");
+ return getOutput().getAbsolutePath();
+}
+
public String _p_sourcepath(String args[]) throws Exception {
+ return list(args, getSourcePath());
+}
+
public String _p_testpath(String args[]) throws Exception {
+ return list(args, getRunpath());
+}
+
The packageattribute
macro returns the value of the specified attribute name for the
+specified package name. These values are only available after analysis is complete. The
+default attribute to return is the version.
The packages
macro provides a query function over the contained packages of a bundle. A simple query language is used to query the packages and filter them.
+For example if you want to export all packages that are annotated with the @org.example.Export
annotation:
-exportcontents: ${packages;ANNOTATED;org.example.Export}
+
(NB. using the macro inside Export-Package
/Private-Package
is an error, because they define the content of the bundle. The packages
macro can only be used in the final manifest calculation.).
All pattern matching is based on fully qualified name and uses the globbing model.
+ +More examples:
+ +# List all packages in the bundle, irrespective of how they were included
+All-Packages: ${packages}
+
+# List all packages, alternative syntax
+All-Packages: ${packages;ANY}
+
+# Export packages containing the substring 'export'
+-exportcontents: ${packages;NAMED;*export*}
+
+# Export packages that contain a version. Useful when wrapping existing bundles while keeping exports intact
+-exportcontents: ${packages;VERSIONED}
+
+# List of packages that were included in the bundle as conditional packages
+Added: ${packages;CONDITIONAL}
+
The following table specifies the available query options:
+ +Query | +Parameter | +Description | +
---|---|---|
ANY | ++ | Matches any package | +
ANNOTATED | +PATTERN | +The package must have an annotation that matches the pattern. The annotation must be either CLASS or RUNTIME retained, and placed on the `package-info.class` for the package. | +
NAMED | +PATTERN | +The package FQN must match the given pattern. | +
VERSIONED | ++ | Packages that are versioned. Usually this means exported packages. | +
CONDITIONAL | ++ | Packages that were included in the bundle as conditional packages. That is, + by using -conditionalpackage or + Conditional-Package. | +
public String _path(String args[]) {
+ List<String> list = new ArrayList<String>();
+ for (int i = 1; i < args.length; i++) {
+ list.addAll(Processor.split(args[i]));
+ }
+ return Processor.join(list, File.pathSeparator);
+}
+
public String _pathseparator(String args[]) {
+ return File.pathSeparator;
+}
+
public String _permissions(String args[]) {
+ StringBuilder sb = new StringBuilder();
+
+ for (String arg : args) {
+ if ("packages".equals(arg) || "all".equals(arg)) {
+ for (PackageRef imp : getImports().keySet()) {
+ if (!imp.isJava()) {
+ sb.append("(org.osgi.framework.PackagePermission \"");
+ sb.append(imp);
+ sb.append("\" \"import\")\r\n");
+ }
+ }
+ for (PackageRef exp : getExports().keySet()) {
+ sb.append("(org.osgi.framework.PackagePermission \"");
+ sb.append(exp);
+ sb.append("\" \"export\")\r\n");
+ }
+ } else if ("admin".equals(arg) || "all".equals(arg)) {
+ sb.append("(org.osgi.framework.AdminPermission)");
+ } else if ("permissions".equals(arg))
+ ;
+ else
+ error("Invalid option in ${permissions}: %s", arg);
+ }
+ return sb.toString();
+}
+
public String _propertiesdir(String[]args) {
+ if ( args.length > 1) {
+ error("propertiesdir does not take arguments");
+ return null;
+ }
+ File pf = getPropertiesFile();
+ if ( pf == null)
+ return "";
+
+ return pf.getParentFile().getAbsolutePath();
+}
+
public String _propertiesname(String[]args) {
+ File pf = getPropertiesFile();
+ if ( pf == null)
+ return "";
+
+ return pf.getName();
+}
+
static String _rand = "${rand;[<min>[;<end>]]}";
+static Random random = new Random();
+
+public long _rand(String args[]) throws Exception {
+ verifyCommand(args, _rand, null, 2, 3);
+
+ int min = 0;
+ int max = 100;
+ if (args.length > 1) {
+ max = Integer.parseInt(args[1]);
+ if (args.length > 2) {
+ min = Integer.parseInt(args[2]);
+ }
+ }
+ int diff = max - min;
+
+ double d = random.nextDouble() * diff + min;
+ return Math.round(d);
+}
+
/**
+ * Generate a random string, which is guaranteed to be a valid Java
+ * identifier (first character is an ASCII letter, subsequent characters are
+ * ASCII letters or numbers). Takes an optional parameter for the length of
+ * string to generate; default is 8 characters.
+ */
+public String _random(String[] args) {
+ int numchars = 8;
+ if (args.length > 1) {
+ try {
+ numchars = Integer.parseInt(args[1]);
+ }
+ catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Invalid character count parameter in ${random} macro.");
+ }
+ }
+
+ synchronized (Processor.class) {
+ if (random == null)
+ random = new Random();
+ }
+
+ char[] letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
+ char[] alphanums = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
+
+ char[] array = new char[numchars];
+ for (int i = 0; i < numchars; i++) {
+ char c;
+ if (i == 0)
+ c = letters[random.nextInt(letters.length)];
+ else
+ c = alphanums[random.nextInt(alphanums.length)];
+ array[i] = c;
+ }
+
+ return new String(array);
+}
+
The range
macro takes a version range mask/template and uses it calculate a range from a given version. The primary reason for the ${range}
macro is to be used in the version policy. With the version policy we have a version of an exported package and we need to calculate the range for that. The rules come from the consumer or provider policy. However, this policy can be overridden on the Import-Package header by specifying the version as a range macro:
Import-Package: com.example.myownpolicy; version="${range;[==,=+)}", *
+
<foo ]>
+ +Since the version for the exported package is set as ${@}
, the macro will calculate the proper semantic range for a provider.
The syntax for the range
macro is:
range ::= ( '\[' |'\( ) mask ',' mask ( '\)' | '\]' )
+mask ::= m ( m ( m )? )? q
+m ::= [0-9=+-~]
+q ::= [0-9=~Ss]
+
The meaning of the characters is:
+ +=
– Keep the version part-
– Decrement the version part+
– Increment the version part[0-9]
– Replace the version part~
– Ignore the version part[Ss]
– If the qualifier equals SNAPSHOT
, then it will return a maven like snapshot version. Maven snapshot versions do not use the .
as the separator but a -
sign. The upper case S
checks case sensitive, the lower case s
is case insensitive. This template character will be treated as the last character in the template and return the version immediately. For example, ${versionmask;=S;1.2.3.SNAPSHOT}
will return 1-SNAPSHOT
./**
+ * Schortcut for version policy
+ *
+ * <pre>
+ * -provide-policy : ${policy;[==,=+)}
+ * -consume-policy : ${policy;[==,+)}
+ * </pre>
+ *
+ * @param args
+ * @return
+ */
+
+static Pattern RANGE_MASK = Pattern.compile("(\\[|\\()(" + MASK_STRING + "),(" + MASK_STRING + ")(\\]|\\))");
+static String _rangeHelp = "${range;<mask>[;<version>]}, range for version, if version not specified lookup ${@}\n"
+ + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
+ + "M ::= '+' | '-' | MQ\n"
+ + "MQ ::= '~' | '='";
+static Pattern _rangePattern[] = new Pattern[] {
+ null, RANGE_MASK
+ };
+
+public String _range(String args[]) {
+ verifyCommand(args, _rangeHelp, _rangePattern, 2, 3);
+ Version version = null;
+ if (args.length >= 3)
+ version = new Version(args[2]);
+ else {
+ String v = domain.getProperty("@");
+ if (v == null)
+ return null;
+ version = new Version(v);
+ }
+ String spec = args[1];
+
+ Matcher m = RANGE_MASK.matcher(spec);
+ m.matches();
+ String floor = m.group(1);
+ String floorMask = m.group(2);
+ String ceilingMask = m.group(3);
+ String ceiling = m.group(4);
+
+ String left = version(version, floorMask);
+ String right = version(version, ceilingMask);
+ StringBuilder sb = new StringBuilder();
+ sb.append(floor);
+ sb.append(left);
+ sb.append(",");
+ sb.append(right);
+ sb.append(ceiling);
+
+ String s = sb.toString();
+ VersionRange vr = new VersionRange(s);
+ if (!(vr.includes(vr.getHigh()) || vr.includes(vr.getLow()))) {
+ domain.error("${range} macro created an invalid range %s from %s and mask %s", s, version, spec);
+ }
+ return sb.toString();
+}
+
The reject macro is an alias to the filterout macro.
+ + +Return the first list where items from the second list are removed.
+ +replace ; <list> ; <regex> [ ; <replacement> [ ; <delimiter> ] ]
+
Replace all parts within elements of the list that match the regular expression regex with the replacement. The replace
macro uses a simple splitter, the regular expression \s*,\s*
, to split the list into elements. So any comma in the input list will be use to split the list into elements. See replacelist for an equivalent macro which has a more sophisticated splitter which takes quoted sections of the string into account.
The replacement can use the $[0-9]
back references defined in the regular expressions. The macro uses element.replaceAll(regex,replacement)
method to do the replacement. The default replacement is the empty string. The default delimiter is “,”.
impls: foo,bar
+${replace;${impls};$;.jar} => foo.jar,bar.jar
+
replacelist ; <list> ; <regex> [ ; <replacement> [ ; <delimiter> ] ]
+
Replace all parts within elements of the list that match the regular expression regex with the replacement. The replacelist
macro uses a sophisticated splitter to split the list into elements. This splitter understands quoted sections within the list and does not split on commas inside the quoted sections.
The replacement can use the $[0-9]
back references defined in the regular expressions. The macro uses element.replaceAll(regex,replacement)
method to do the replacement. The default replacement is the empty string. The default delimiter is “,”.
impls: foo;version="[1,2)", bar;version="[1.2,2)"
+${replacelist;${list;impls};$;\\;strategy=highest} =>
+ foo;version="[1,2)";strategy=highest,bar;version="[1.2,2)";strategy=highest
+
replacestring ; <string> ; <regex> [ ; <replacement> ]
+
Replace all parts of the string that match the regular expression regex with the replacement. The replacement can use the $[0-9]
back references defined in the regular expressions. The macro uses string.replaceAll(regex,replacement)
method to do the replacement. The default replacement is the empty string.
description: This is, possibly, the best implementation ever!
+${replacestring;${description};possibly;definitely} =>
+ This is, definitely, the best implementation ever!
+
Returns the absolute file system paths to the specified artifacts in the repositories.
+ +BSN is be a comma-separated list of bundle symbolic names. If the artifact +is not a bundle, then the synthetic bundle symbolic names of groupId:artifactId +can be used. Normally only a single bundle symbolic name is used since the remainder of the options apply to all the bundle symbolic names.
+ +VERSION is a version range for the artifact. Special values supported are:
+project
- This return the built artifact from a project in the Bnd workspace.snapshot
- Synonym for project
.latest
- The highest version available in a project in the Bnd workspace or the repositories. The built artifact from a project in the Bnd workspace is always used if it exists under the assumption the Bnd workspace is always building the latest version of the artifact.
+If the version range is not specified, the version range [0,∞)
is used.STRATEGY is the selection strategy to be used when multiple artifacts with the bundle symbolic name exist within the version range. The strategies supported are:
+HIGHEST
- The highest version for the artifact which is included by the version range. This is the default strategy and is the strategy always used by the special version range latest
.LOWEST
- The lowest version for the artifact which is included by the version range.EXACT
- When this strategy is used, the version range must be a single version which is the version which is searched for. If multiple repositories contain the the exact version of the artifact, the artifact from the first repository is used.public Object _repodigests(String[] args) throws Exception {
+ Macro.verifyCommand(args, "${repodigests;[;<repo names>]...}, get the repository digests", null, 1, 10000);
+ List<RepositoryPlugin> repos = getRepositories();
+ if (args.length > 1) {
+ repos: for (Iterator<RepositoryPlugin> it = repos.iterator(); it.hasNext();) {
+ String name = it.next().getName();
+ for (int i = 1; i < args.length; i++) {
+ if (name.equals(args[i])) {
+ it.remove();
+ continue repos;
+ }
+ }
+ it.remove();
+ }
+ }
+ List<String> digests = new ArrayList<String>();
+ for (RepositoryPlugin repo : repos) {
+ try {
+ // TODO use RepositoryDigest interface when it is widely
+ // implemented
+ Method m = repo.getClass().getMethod("getDigest");
+ byte[] digest = (byte[]) m.invoke(repo);
+ digests.add(Hex.toHexString(digest));
+ }
+ catch (Exception e) {
+ if (args.length != 1)
+ error("Specified repo %s for digests is not found", repo.getName());
+ // else Ignore
+ }
+ }
+ return join(digests, ",");
+}
+
public String _repos(@SuppressWarnings("unused")
+String args[]) throws Exception {
+ List<RepositoryPlugin> repos = getPlugins(RepositoryPlugin.class);
+ List<String> names = new ArrayList<String>();
+ for (RepositoryPlugin rp : repos)
+ names.add(rp.getName());
+ return join(names, ", ");
+}
+
Return the first list where items not in the second list are removed.
+ +static String _reverse = "${reverse;<list>[;<list>...]}";
+
+public String _reverse(String args[]) throws Exception {
+ verifyCommand(args, _reverse, null, 2, Integer.MAX_VALUE);
+
+ ExtList<String> list = toList(args, 1, args.length);
+ Collections.reverse(list);
+ return Processor.join(list);
+}
+
The select macro is an alias to the filter macro.
+ + +public String _separator(String args[]) {
+ return File.separator;
+}
+
/**
+ * SHA1 macro
+ */
+
+static String _sha1Help = "${sha1;path}";
+
+public String _sha1(String args[]) throws Exception {
+ Macro.verifyCommand(args, _sha1Help, new Pattern[] {
+ null, null, Pattern.compile("base64|hex")
+ }, 2, 3);
+ Digester<SHA1> digester = SHA1.getDigester();
+ Resource r = dot.getResource(args[1]);
+ if (r == null)
+ throw new FileNotFoundException("From sha1, not found " + args[1]);
+
+ IO.copy(r.openInputStream(), digester);
+ return Base64.encodeBase64(digester.digest().digest());
+}
+
public final static String _sizeHelp = "${size;<collection>;...}, count the number of elements (of all collections combined)";
+
+public int _size(String args[]) {
+ verifyCommand(args, _sizeHelp, null, 2, 16);
+ int size = 0;
+ for (int i = 1; i < args.length; i++) {
+ ExtList<String> l = ExtList.from(args[i]);
+ size += l.size();
+ }
+ return size;
+}
+
static String _sjoinHelp = "${sjoin;<separator>;<list>...}";
+public String _sjoin(String args[]) throws Exception {
+ verifyCommand(args, _sjoinHelp, null, 2, Integer.MAX_VALUE);
+
+ List<String> result = new ArrayList<String>();
+ for (int i = 2; i < args.length; i++) {
+ Processor.split(args[i], result);
+ }
+ return Processor.join(args[1], result);
+}
+
public String _sort(String args[]) {
+ verifyCommand(args, _sortHelp, null, 2, Integer.MAX_VALUE);
+
+ List<String> result = new ArrayList<String>();
+ for (int i = 1; i < args.length; i++) {
+ Processor.split(args[i], result);
+ }
+ Collections.sort(result);
+ return Processor.join(result);
+}
+
static String _split = "${split;<regex>[;<target>...]}";
+
+public String _split(String args[]) throws Exception {
+ verifyCommand(args, _split, null, 2, Integer.MAX_VALUE);
+
+ List<String> collected = new ArrayList<String>();
+ for (int n = 2; n < args.length; n++) {
+ String value = args[n];
+ String[] split = value.split(args[1]);
+ for (String s : split)
+ if (!s.isEmpty())
+ collected.add(s);
+ }
+ return Processor.join(collected);
+}
+
static String _startswith = "${startswith;<string>;<prefix>}";
+public String _startswith(String args[]) throws Exception {
+ verifyCommand(args, _startswith, null, 3, 3);
+ if (args[1].startsWith(args[2]))
+ return args[1];
+ else
+ return "";
+}
+
static final String _stemHelp = "${stem;<string>}";
+
+public String _stem(String[] args) throws Exception {
+ verifyCommand(args, _stemHelp, null, 2, 2);
+ String name = args[1];
+ int n = name.indexOf('.');
+ if (n < 0)
+ return name;
+ return name.substring(0, n);
+}
+
static String _sublist = "${sublist;<start>;<end>;<list>}";
+
+public String _sublist(String args[]) throws Exception {
+ verifyCommand(args, _sublist, null, 4, Integer.MAX_VALUE);
+
+ int start = Integer.parseInt(args[1]);
+ int end = Integer.parseInt(args[2]);
+ ExtList<String> list = toList(args, 3, Integer.MAX_VALUE);
+
+ if (start < 0)
+ start = list.size() + start + 1;
+
+ if (end < 0)
+ end = list.size() + end + 1;
+
+ if (start > end) {
+ int t = start;
+ start = end;
+ end = t;
+ }
+
+ return Processor.join(list.subList(start, end));
+}
+
subst ; <target> ; <regex> [ ; <replacement> [ ; <count> ] ]
+
Substitute the substrings that match the <regex>
in the the <target>
with the replacement. The default replacement is the empty string. The default count is all.
${subst;foo.bar;.bar} => foo
+
static String _substring = "${substring;<string>;<start>[;<end>]}";
+
+public String _substring(String args[]) throws Exception {
+ verifyCommand(args, _substring, null, 3, 4);
+
+ String string = args[1];
+ int start = Integer.parseInt(args[2].equals("") ? "0" : args[2]);
+ int end = string.length();
+
+ if (args.length > 3) {
+ end = Integer.parseInt(args[3]);
+ if (end < 0)
+ end = string.length() + end;
+ }
+
+ if (start < 0)
+ start = string.length() + start;
+
+ if ( start > end ) {
+ int t = start;
+ start = end;
+ end = t;
+ }
+
+ return string.substring(start, end);
+}
+
static String _sum = "${sum;<list>[;<list>...]}";
+
+public String _sum(String args[]) throws Exception {
+ verifyCommand(args, _sum, null, 2, Integer.MAX_VALUE);
+
+ List<String> list = toList(args, 1, args.length);
+ double d = 0;
+
+ for (String s : list) {
+ double v = Double.parseDouble(s);
+ d += v;
+ }
+ return toString(d);
+}
+
public String _system(String args[]) throws Exception {
+ return system_internal(false, args);
+}
+
+public String _system_allow_fail(String args[]) throws Exception {
+ String result = "";
+ try {
+ result = system_internal(true, args);
+ }
+ catch (Throwable t) {
+ /* ignore */
+ }
+ return result;
+}
+
+ /**
+ * System command. Execute a command and insert the result.
+ *
+ * @param args
+ * @param help
+ * @param patterns
+ * @param low
+ * @param high
+ */
+public String system_internal(boolean allowFail, String args[]) throws Exception {
+ if (nosystem)
+ throw new RuntimeException("Macros in this mode cannot excute system commands");
+
+ verifyCommand(args, "${" + (allowFail ? "system-allow-fail" : "system")
+ + ";<command>[;<in>]}, execute a system command", null, 2, 3);
+ String command = args[1];
+ String input = null;
+
+ if (args.length > 2) {
+ input = args[2];
+ }
+
+ if ( File.separatorChar == '\\')
+ command = "cmd /c \"" + command + "\"";
+
+
+ Process process = Runtime.getRuntime().exec(command, null, domain.getBase());
+ if (input != null) {
+ process.getOutputStream().write(input.getBytes("UTF-8"));
+ }
+ process.getOutputStream().close();
+
+ String s = IO.collect(process.getInputStream(), "UTF-8");
+ int exitValue = process.waitFor();
+ if (exitValue != 0)
+ return exitValue + "";
+
+ if (exitValue != 0) {
+ if (!allowFail) {
+ domain.error("System command " + command + " failed with exit code " + exitValue);
+ } else {
+ domain.warning("System command " + command + " failed with exit code " + exitValue + " (allowed)");
+
+ }
+ }
+
+ return s.trim();
+}
+
public String _system(String args[]) throws Exception {
+ return system_internal(false, args);
+}
+
+public String _system_allow_fail(String args[]) throws Exception {
+ String result = "";
+ try {
+ result = system_internal(true, args);
+ }
+ catch (Throwable t) {
+ /* ignore */
+ }
+ return result;
+}
+
+ /**
+ * System command. Execute a command and insert the result.
+ *
+ * @param args
+ * @param help
+ * @param patterns
+ * @param low
+ * @param high
+ */
+public String system_internal(boolean allowFail, String args[]) throws Exception {
+ if (nosystem)
+ throw new RuntimeException("Macros in this mode cannot excute system commands");
+
+ verifyCommand(args, "${" + (allowFail ? "system-allow-fail" : "system")
+ + ";<command>[;<in>]}, execute a system command", null, 2, 3);
+ String command = args[1];
+ String input = null;
+
+ if (args.length > 2) {
+ input = args[2];
+ }
+
+ if ( File.separatorChar == '\\')
+ command = "cmd /c \"" + command + "\"";
+
+
+ Process process = Runtime.getRuntime().exec(command, null, domain.getBase());
+ if (input != null) {
+ process.getOutputStream().write(input.getBytes("UTF-8"));
+ }
+ process.getOutputStream().close();
+
+ String s = IO.collect(process.getInputStream(), "UTF-8");
+ int exitValue = process.waitFor();
+ if (exitValue != 0)
+ return exitValue + "";
+
+ if (exitValue != 0) {
+ if (!allowFail) {
+ domain.error("System command " + command + " failed with exit code " + exitValue);
+ } else {
+ domain.warning("System command " + command + " failed with exit code " + exitValue + " (allowed)");
+
+ }
+ }
+
+ return s.trim();
+}
+
The template
macro is intended to make it very simple to generate new information based on a Parameters. Parameters
+are the Bnd workhorse to store information. The macro takes the following arguments:
macro name The name of the macro (not the value)
+template+ the templates (these are joined with the ';' as separator)
+
The template is expanded for each entry of the Parameters. They key can be referred by ${@}
and the attributes can be
+referred by ${@<name>}
, where the name is the name of the attribute. All entries are then joined with a comma (,
) as
+separator.
For example, the following example shows how to extract and attribute as a list:
+ +bnd shell
+> parameters = key;attr=1, key;attr="2"
+> ${template;parameters;${@attr}}
+1,2
+
The template
macro takes the NAME
of the macro that contains the value. I.e. it does not take the expanded value as
+argument. The reason is that the referred macro gets merged and decorated. Merge takes all properties that start with the given name.
> parameters = key;attr=1, key;attr="2"
+> parameters.extra = KEY;attr=3, KEY;attr="4"
+> ${template;parameters;${@attr}}
+1,2,3,4
+
Decorate means that the property with the same name but with a +
at the end will be matched with the value. In this property the
+key is a glob. It is matched against the key from the original merged properties. Any matching properties get the attributes
+from the decorator.
> parameters = a;attr=1, b;attr="2"
+> parameters.extra = c;attr=3, d;attr="4"
+> parameters+ = (c|d);attr=X
+> ${template;parameters;${@attr}}
+1,2,X,X
+
The macro accepts any number of arguments after the macro name. These values are joined with a semi-colon as separator.
+The reason is that then the ;
in the templates do not have to be escaped:
bnd shell
+> parameters = a;attr=1, b;attr="2"
+> ${template;parameters;${@};key=${@};${@}=${@attr}}
+a;a=1,b;b=2
+
/**
+ * Return the name of the properties file
+ */
+
+public String _thisfile(String[] args) {
+ if (propertiesFile == null) {
+ error("${thisfile} executed on a processor without a properties file");
+ return null;
+ }
+
+ return IO.absolutePath(propertiesFile);
+}
+
static String _toclasspathHelp = "${toclasspath;<list>[;boolean]}, convert a list of class names to paths";
+
+public String _toclasspath(String args[]) {
+ verifyCommand(args, _toclasspathHelp, null, 2, 3);
+ boolean cl = true;
+ if (args.length > 2)
+ cl = Boolean.valueOf(args[2]);
+
+ Collection<String> names = Processor.split(args[1]);
+ Collection<String> paths = new ArrayList<String>(names.size());
+ for (String name : names) {
+ String path = name.replace('.', '/') + (cl ? ".class" : "");
+ paths.add(path);
+ }
+ return Processor.join(paths, ",");
+}
+
static String _toclassnameHelp = "${classname;<list of class names>}, convert class paths to FQN class names ";
+
+public String _toclassname(String args[]) {
+ verifyCommand(args, _toclassnameHelp, null, 2, 2);
+ Collection<String> paths = Processor.split(args[1]);
+
+ List<String> names = new ArrayList<String>(paths.size());
+ for (String path : paths) {
+ if (path.endsWith(".class")) {
+ String name = path.substring(0, path.length() - 6).replace('/', '.');
+ names.add(name);
+ } else if (path.endsWith(".java")) {
+ String name = path.substring(0, path.length() - 5).replace('/', '.');
+ names.add(name);
+ } else {
+ domain.warning("in toclassname, " + args[1] + " is not a class path because it does not end in .class");
+ }
+ }
+ return Processor.join(names, ",");
+}
+
static String _tolower = "${tolower;<target>}";
+
+public String _tolower(String args[]) throws Exception {
+ verifyCommand(args, _tolower, null, 2, 2);
+
+ return args[1].toLowerCase();
+}
+
static String _toupper = "${toupper;<target>}";
+
+public String _toupper(String args[]) throws Exception {
+ verifyCommand(args, _tolower, null, 2, 2);
+
+ return args[1].toUpperCase();
+}
+
static String _trim = "${trim;<target>}";
+
+public String _trim(String args[]) throws Exception {
+ verifyCommand(args, _trim, null, 2, 2);
+
+ return args[1].trim();
+}
+
public String _tstamp(String args[]) {
+ String format = "yyyyMMddHHmm";
+ long now = System.currentTimeMillis();
+ TimeZone tz = TimeZone.getTimeZone("UTC");
+
+ if (args.length > 1) {
+ format = args[1];
+ }
+ if (args.length > 2) {
+ tz = TimeZone.getTimeZone(args[2]);
+ }
+ if (args.length > 3) {
+ now = Long.parseLong(args[3]);
+ }
+ if (args.length > 4) {
+ domain.warning("Too many arguments for tstamp: " + Arrays.toString(args));
+ }
+
+ SimpleDateFormat sdf = new SimpleDateFormat(format);
+ sdf.setTimeZone(tz);
+
+ return sdf.format(new Date(now));
+}
+
public String _unescape(String args[]) {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 1; i < args.length; i++) {
+ sb.append(args[i]);
+ }
+
+ for (int j = 0; j < sb.length() - 1; j++) {
+ if (sb.charAt(j) == '\\') {
+ switch (sb.charAt(j + 1)) {
+
+ case 'n' :
+ sb.replace(j, j + 2, "\n");
+ break;
+
+ case 'r' :
+ sb.replace(j, j + 2, "\r");
+ break;
+
+ case 'b' :
+ sb.replace(j, j + 2, "\b");
+ break;
+
+ case 'f' :
+ sb.replace(j, j + 2, "\f");
+ break;
+
+ case 't' :
+ sb.replace(j, j + 2, "\t");
+ break;
+
+ default :
+ break;
+ }
+ }
+ }
+ return sb.toString();
+}
+
Split all the given lists on their commas, combine them in one list and remove any duplicates. The ordering is not preserved, see [${sort}][#sort] For example:
+ + ${unique; 1,2,3,1,2; 1,2,4 } ~ “2,4,3,1” + +static String _uniqHelp = "${uniq;<list> ...}";
+
+public String _uniq(String args[]) {
+ verifyCommand(args, _uniqHelp, null, 1, Integer.MAX_VALUE);
+ Set<String> set = new LinkedHashSet<String>();
+ for (int i = 1; i < args.length; i++) {
+ Processor.split(args[i], set);
+ }
+ return Processor.join(set, ",");
+}
+
If the specified uri is not absolute or is a file
scheme uri,
+the specified uri is resolved against either the second uri, if specified, or
+the base uri of the Processor and the resolved uri is returned.
Otherwise, the specified uri is returned.
+ +So ${uri;.}
will return the base uri of a project if used in a project’s bnd file,
+for example bnd.bnd
, or the base uri of the workspace if used in a workspace’s bnd
+file, for example cnf/build.bnd
.
The user macro is an alias to the global macro.
+ + +Compare two version strings. The result “0” means the two versions are equal, “1” means the first version is greater than the second version, “-1” means the first version is less than the second version.
+ +${vcompare;versionA;versionB}
+
The version macro is an alias to the versionmask macro for backward compatibility reasons.
+ + +The version_cleanup
macro takes a version-ish string and cleans it up, producing the OSGi Version syntax.
For example, a Maven version can be turned into the OSGi Version syntax:
+ +${version_cleanup;1.2.3-SNAPSHOT} -> 1.2.3.SNAPSHOT
+
null
, the version returned is 0
.(\\(|\\[)\\s*([-.\\w]+)\\s*,\\s*([-.\\w]+)\\s*(\\]|\\))
(with java.util.regex.Pattern.DOTALL
enabled) a sufficiently cleaned up OSGi Version range is returned.(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^\\p{Alnum}](.*))?
(with java.util.regex.Pattern.DOTALL
enabled) a sufficiently cleaned up OSGi Version string is returned.The versionmask
macro takes a template (MASK) and a version. It then uses the template
+to modify the version. This is useful to get rid of parts of the version. For example,
+to get rid of the qualifier the following macro is useful:
${versionmask;===;1.2.3.awfulqualifier} -> 1.2.3
+
It is also possible to modify the template. For example, if the next minor version is +sought, then the following macro is useful:
+ +${versionmask;=+;1.2.3.awfulqualifier} -> 1.3
+
The syntax for the versionmask
macro is:
versionmask ::= mask
+mask ::= m ( m ( m ( q )? )? )?
+m ::= [0-9=+-~]
+q ::= [0-9=~Ss]
+
The MASK can consists of 1 to 4 template characters. Each character maps to a version +part.The first character maps to the major, the second to the minor, the third to the +micro, and the last to the qualifier. These characters can be:
+ +=
– Keep the version part-
– Decrement the version part+
– Increment the version part[0-9]
– Replace the version part~
– Ignore the version partS
– If the qualifier equals SNAPSHOT
or ends with -SNAPSHOT
, then the qualifier
+will be ignored and the resulting version will be suffixed with -SNAPSHOT
. This
+results in a maven version which is not a valid OSGi version. Otherwise, the existing
+qualifier, if any, will be kept. For example, ${versionmask;===S;1.2.3.SNAPSHOT}
will
+return 1.2.3-SNAPSHOT
and ${versionmask;===S;1.2.3.QUAL}
will return 1.2.3.QUAL
.s
– If the qualifier equals SNAPSHOT
or ends with -SNAPSHOT
, then the qualifier
+will be ignored and the resulting version will be suffixed with -SNAPSHOT
. This
+results in a maven version which is not a valid OSGi version. Otherwise, the existing
+qualifier, if any, will be ignored. For example, ${versionmask;===s;1.2.3.SNAPSHOT}
+will return 1.2.3-SNAPSHOT
and ${versionmask;===s;1.2.3.QUAL}
will return 1.2.3
.In many places in bnd, the ${@}
macro is set as an implicit variable. For example, if
+the bnd analyzer calculates the import range it sets this to the version of the package
+that is imported. Therefore, if the version is not specified in this macro as the second
+argument then the ${@}
macro is expanded and used as the value for the version. A use
+of this is in the Import Package statement. There you can use the ${versionmask}
macro
+to clean up the imported version. (Though the range macro is better suited for
+this.)
Import-Package com.exmaple.foo;version=${versionmask;==}
+
This macro is also available under the name version
. The name was changed because this
+often collided with macros that were defined by the user.
All the version strings in the lists are compared and the maximum version is returned.
+ +All the version strings in the lists are compared and the minimum version is returned.
+ +public String _warning(String args[]) {
+ for (int i = 1; i < args.length; i++) {
+ domain.warning(process(args[i]));
+ }
+ return "";
+}
+
public String _workspace(@SuppressWarnings("unused") String args[]) {
+ return getBase().getAbsolutePath();
+}
+
Aspects make it possible to centralize cross cutting concerns.
+ +The biz.aQute.aspectj.plugin.AspectJ plugin allows you to weave an executable JAR during export. It +provides a new export type:
+ +bnd.executablejar.aspectj
+
This export will use the bnd default export type to create an executable JAR. It will then weave +all the bundles inside this executable jar according to some aspect bundles. An aspect bundle +is created with either the ajc compiler or uses the annotations based definition of AspectJ code.
+ +Any aspect bundles are compiled to convert the annotations to actual code and added to the +dependencies.
+ +The export function has the following arguments:
+ +match
– A globbing expression on the bundle names to weave. The expression matches if the glob can be
+found. I.e. it is not necessary to full match since it is applied to the path inside the bundle. The default
+is all bundles (*).aspectpath
– List of bundle symbolic names of aspect bundles that need to be appliedajc
– A comma separated list of ajc optionsThe ajc is invoked with the same javac
source and target as defined in the workspace.
To enable the AspectJ plugin, add a plugin in the worksapce build.bnd
file. (There is a context
+menu entry for this.)
-plugins \
+ ...., \
+ aQute.bnd.aspectj.plugin.AspectJ
+
The following code defines an aspect with the annotation model in a bundle called ‘aspect’:
+
+ @Aspect
+ public class AspectHandler {
+ @Before("execution(void *.start(org.osgi.framework.BundleContext))")
+ public void myadvice(JoinPoint jp) {
+ BundleContext c = (BundleContext) jp.getArgs()[0];
+ System.out.println("Starting bundle : " + c.getBundle());
+ }
+ }
+
The cutpoint will print out a message on each invocation of a Bundle Activator start method.
+ +To test this create a test.bndrun
in an export project:
-runfw: org.apache.felix.framework;version='[6.0.2,6.0.2]'
+-runee: JavaSE-1.8
+-runrequires: \
+ bnd.identity;id='exporter',\
+ bnd.identity;id='org.apache.felix.gogo.command',\
+ bnd.identity;id='org.apache.felix.gogo.runtime',\
+ bnd.identity;id='org.apache.felix.gogo.shell'
+
You can then resolve it and run it to see if the shell works.
+ +Next is to export the test.bndrun
in the bnd.bnd
file of a project:
-export \
+ test.bndrun; \
+ type = bnd.executablejar.aspectj; \
+ match = *; \
+ aspectpath = aspect
+
This will export the the executable JAR while it applies the bundle aspect
to all bundles.
The Aspectj compiler is trying hard to crush the fences of modularity. Currently the bundles are woven one by one, which means +some dependencies are not visible to them. The plugin needs some experience and feedback to grow. However, it already seems +quite usable in its current incarnation.
+ +One issue is the ‘compiling’ of the annotations that happens on the -aspectpath
. This means that the ‘compiled’ (not
+woven) aspects must be in the output. The rules are not always completely clear and we’re working on making this
+work in all OSGi scenarios. Please provide feedback.
Bnd stores information about bundles it knows in an index, following the OSGi Repository Service Specification. The LocalIndexedRepo
can be used as a repository to release bundles in or where bundles can be added manually (in bndtools via drag and drop).
The class name of the plugin is aQute.bnd.deployer.repository.LocalIndexRepo
. It can take the following configuration properties:
| Property | Type | Default | Description |
+| ———– | ——— | ——- | ———————————————————— |
+| name
| NAME | | The name of the repository. |
+|
local |
STRING | | The directory the index and added bundles will be stored in |
+|
locations |
STRING | | The location to store the _index_ file. |
+|
readonly |
BOOLEAN |
false | Blocks write access to the repository |
+|
overwrite |
BOOLEAN |
false | Enable overwrite of existing Bundles. By default Bundles with the same Versions will not be added again |
+|
onlydirs |
BOOLEAN |
false | A comma separated list of directories relative to the
localproperty to be whitelisted for this repo (used when the index is created) |
+|
.cache |
STRING | | |
+|
pretty |
BOOLEAN |
false | |
+|
phase |
STRING | | |
+|
timeout |
integer | | |
+|
online |
BOOLEAN |
false | |
+|
type |
STRING` | | |
aQute.bnd.deployer.repository.LocalIndexedRepo; \
+ name = Release; \
+ pretty = true; \
+ local = ${build}/release
+
The Maven Bnd Repository plugin provides a full interface to the Maven local repository in ~/.m2/repository
and remote repositories like Nexus or Artifactory. And it provides of course full access to Maven Central. It implements the standard bnd Repository Plugin and can provide an OSGi Repository for resolving.
To access Maven Central use the following configuration:
+ +-plugin.central = \
+ aQute.bnd.repository.maven.provider.MavenBndRepository; \
+ releaseUrl=https://repo.maven.apache.org/maven2/; \
+ index=${.}/central.maven; \
+ name="Central"
+
You can add Group:Artifact:Version
coordinates in the central.maven
file. The file can contain comments, empty lines, and can use macros per line. That is, you cannot create a macro with a load of GAV’s.
Releasing to maven Central requires usually a couple of steps. For one you usually go through a staging repositroy like the staging nexus of sonartype. They perform a couple of checks and you manually have to clear the release via their web frontend.
+ +In case you version your bundles individually, e.g. sonartype will not complain if a Version of one Artifact in your staging repo already exists. The release will simply not work. Thus you can define a staging repository. The Release process will check against the releaseUrl if something already exists, but will upload to the staging URL.
+ +A configuration can look like this:
+ +-plugin.release = \
+ aQute.bnd.repository.maven.provider.MavenBndRepository; \
+ releaseUrl=https://repo.maven.apache.org/maven2/; \
+ stagingUrl=https://oss.sonatype.org/service/local/staging/deploy/maven2/; \
+ index=${.}/release.maven; \
+ name="Release"
+
To use your local Maven repository (~/.m2/repository
) you can define the following plugin:
-plugin.local = \
+ aQute.bnd.repository.maven.provider.MavenBndRepository; \
+ index=${.}/local.maven; \
+ name="Local"
+
To use a remote release repository based on Nexus or Artifactory you can define the following plugin:
+ +-plugin.release = \
+ aQute.bnd.repository.maven.provider.MavenBndRepository; \
+ releaseUrl=http://localhost:8081/nexus/content/repositories/releases/ ; \
+ snapshotUrl=http://localhost:8081/nexus/content/repositories/snapshots/ ; \
+ index=${.}/release.maven; \
+ name="Release"
+
If you use a remote repository then you must configure the credentials. This is described in -connection-settings. Placing the following XML in ~/.bnd/settings.xml
will provide you with the default Nexus credentials:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+ http://maven.apache.org/xsd/settings-1.0.0.xsd">
+ <servers>
+ <server>
+ <id>http://localhost:8081</id>
+ <username>admin</username>
+ <password>admin123</password>
+ </server>
+ </servers>
+</settings>
+
Notice that the id must match the scheme, the host, and the port if not the default port for the scheme.
+ +The class name of the plugin is aQute.bnd.repository.maven.provider.MavenBndRepository
. It can take the following configuration properties:
Property | +Type | +Default | +Description | +
---|---|---|---|
releaseUrl |
+ URI |
+ + | Comma separated list of URLs to the repositories of released artifacts. | +
snapshotUrl |
+ URI |
+ + | Comma separated list of URLs to the repositories of snapshot artifacts. | +
stagingUrl |
+ URI |
+ + | A single URL to the repositories staging repository. THis is required, e.g. for a release to maven central, which usually goes through a staging repository. | +
local |
+ PATH |
+ ~/.m2/repository |
+ The file path to the local Maven repository. | +
+ | + | + | If specified, it should use forward slashes. If the directory does not exist, the plugin will attempt to create it. | +
+ | + | + | The default can be overridden with the maven.repo.local System property. |
+
readOnly |
+ true|false |
+ false |
+ If set to truthy then this repository is read only. | +
name |
+ NAME |
+ Maven |
+ The name of the repository. | +
index |
+ PATH |
+ cnf/<name>.mvn |
+ The path to the index file. The index file is a list of Maven coordinates. | +
source |
+ STRING |
+ org.osgi:org.osgi.service.log:1.3.0 org.osgi:org.osgi.service.log:1.2.0 |
+ A space, comma, semicolon, or newline separated GAV string. | +
noupdateOnRelease |
+ true|false |
+ false |
+ If set to truthy then this repository will not update the index when a non-snapshot artifact is released. |
+
poll.time |
+ integer |
+ 5 seconds | +Number of seconds between checks for changes to the index file. If the value is negative or the workspace is in batch/CI mode, then no polling takes place. |
+
multi |
+ NAME |
+ + | Comma separated list of extensions to be searched for indexing containing bundles. For example, a zip file could comprise further bundles. Hence, this zip artifact can be referenced in this plugin for indexing the internal JARs. | +
If no releaseUrl
nor a snapshotUrl
are specified then the repository is local only.
For finding archives, both URLs are used. For releasing, only the first or the stagingUrl
is used.
The index
file specifies a view on the remote repository, it scopes it. Since we use the bnd repositories to resolve against, it is impossible to resolve against the world. The index file falls under source control, it is stored in the source control management system. This guarantees that at any time the project is checked out it has the same views on its repository. This is paramount to prevent build breackages due to changes in repositories.
Alternative, the GAV’s can be specified in the file where the repository is defined with the source
configuration property. This is a string separated by either whitespace, commas, semicolons, or any combination thereof.
Both the index file and the source configuration can be replaced by macros. The only difference is that the source can use macros for more than one GAV while the indexFile is processed per line and that line must deliver at most single GAV.
+ +The index file contains a list of coordinates. A coordinate specifies an archive in a Maven revison. An archive is a ZIP, POM, JAR, or any other type of file. In Maven, these files are addressed within a revision with an extension and a classifier. The extension indicates the type of the file and the classifier is a modifier that makes the name unique in the project. A revision is the combination of a program and a version, where the program is the combination of groupId and artifactId.
+ +For an archive, the extension has a default of jar
and the classifier is by default not set (either null
or empty). The syntax for the coordinates is therefore:
group ':' artifact ( ':' extension ( ':' classifier )? )? ':' version ( '-SNAPSHOT` )?
+
Valid coordinates are:
+ +group:artifact:1.0-SNAPSHOT
+commons-cli:commons-cli:jar:sources:1.3.1
+commons-lang:commons-lang:2.6
+commons-logging:commons-logging:1.2
+org.osgi:osgi.core:6.0.0
+org.osgi:osgi.annotation:6.0.1
+
The file can contain comments (start the line with #
), empty lines and also macros. The domain of the macros is the Workspace. A comment may also be placed after the GAV.
# This is a comment
+${osgi}:${osgi}.service.log:1.4.0
+
Maven supports a local repository in ~/.m2/repository
. All repositories will install through a local repository. The default is the same repository as Maven but this can be overridden with the local
configuration property.
It is possible to define a Maven Bnd Repository without a releaseUrl
or snapshotUrl
. In that case only access to the local repository is provided. Trying to release remotely will be an error for such a repository.
The -buildrepo instruction can be pointed to such a local repository; it will then store a JAR in the local repository after every build. This can be useful if there are Maven projects that consume the output of a bnd workspace.
+ +In bnd, releasing is done by moving the JARs into a special release repository after they’ve been approved. That is, the location of a JAR defines if it is released, there is no need to reflect the release status in the version.
+ +In Maven, the release status is modeled in the version. To support the staging model, versions can end in -SNAPSHOT
. Snapshots are treated very differently in the release process. The most important difference is that snapshot versions can overwrite previous versions.
In the release cycle, a JAR is put
to release repository. The project of the released JAR is provided as context, this means it can inherit from the project and workspace. This plugin will read the -maven-release instruction from the context. This can result in generating source and javadoc jars. By default, a local only release only installs the actual binary.
To properly release the revision we need to know Maven specific information that is normally specified in the Maven pom. The bnd build can construct a pom using the -pom instruction. This instruction creates a pom in the JAR at META-INF/group/artifact/pom.xml
. This plugin requires such a pom to be present. If it is not present, the -maven-release instruction must provide a PATH to an alternative pom. If no pom can be found then this is aborts the release.
In general, the plugin will first install the JARs and its constituents into the local
revision directory, using the Maven naming rules. If this is successful, it uploads the files to the remote repository using the remote naming rules. It will then update the maven-metadata
to reflect the new release.
In Maven, revisions that end in -SNAPSHOT
are treated special in many places. In bnd, we support this model with the -snapshot and -pom instructions. If a snapshotUrl
is specified, then versions that end in SNAPSHOT
are released to that URL.
The Maven Bnd Repository uses the bnd Http Client. See the -connection-settings instruction for how to set the proxy and authentication information.
+ +The repository view in the IDE will show detailed information when you hover the mouse over the the repository entry, the program entry, or the revision entry.
+ +You can add new entries by:
+ +index
file. The repository will be updated immediatelysearch.maven.org
are also supported.The OBR repository file is an XML-based representation of bundle meta-data.
+ +The biz.aQute.resolve.obr.plugin.ObrExporter
plugin allows you to export the OBR index XML file from a bndrun definition. It provides a new export type:
biz.aQute.resolve.obr.plugin.ObrExporter
+
The export function has the following arguments:
+ +outputdir
– The output directory where the OBR index XML file will be generated. If not set, the XML file will be generated in the configured target directory by default.name
– The name of the generated OBR index XML file. By default, the name will be set to the name of the bndrun file.excludesystem
– Flag to include the system bundle capabilities in the generated OBR index XML file. By default, the system bundle capabilities are excluded; that is, the framework bundle capabilities will not be part of the generated OBR XML file.To enable the OBR Exporter plugin, add a plugin in the workspace build.bnd
file. (There is a context
+menu entry for this.)
-plugins \
+ ...., \
+ biz.aQute.resolve.obr.plugin.ObrExporter
+
The following snippet will export the OBR Index XML:
+ +-export: \
+ my-custom.bndrun; \
+ type = bnd.obr.exporter; \
+ name = sample.xml; \
+ excludesystem = true
+
This will export the metadata (requirements and capabilities) of the bundles (including the framework bundle) specified in -runbundles
section of the bndrun file to OBR Index XML file sample.xml
.
The OSGi Repository can read index files as specified by the OSGi Repository Service Specification and is a read only repository.
+ +The class name of the plugin is aQute.bnd.repository.osgi.OSGiRepository
. It can take the following configuration properties:
Property | +Type | +Default | +Description | +
---|---|---|---|
name |
+ NAME |
+ + | The name of the repository. | +
locations |
+ STRING |
+ + | A Comma separate list of URLs point to an OSGi Resource file. | +
cache |
+ STRING |
+ The workspace cache folder | +The location, the downloaded bundles are stored. | +
max.stale |
+ integer |
+ one year | +Bnd has it’s own download cache. Max stale configures for how long the downloaded index file stays in the internal download cache. | +
poll.time |
+ integer |
+ 5 seconds | +Number of seconds between checks for polls on the index file. If the value is negative or the workspace is in batch/CI mode, then no polling takes place. |
+
To set up the OSGi Repository
use:
aQute.bnd.repository.osgi.OSGiRepository;\
+ locations=https://devel.data-in-motion.biz/public/repository/gecko/release/geckoREST/index.xml;\
+ max.stale=-1;\
+ poll.time=86400;\
+ name=GeckoJaxRsWhiteboard;\
+ cache=${build}/cache/GeckoREST,\
+
##
+ +The Bnd Export plugin is a powerful tool that enables the export of a p2 repository. This manual will guide you through the process of using the plugin effectively.
+ +The plugin is activated by adding it to the -plugin
clauses of the build.bnd
. It takes no configuration. In the Bndtools, there is a context menu to help.
-plugin \
+ aQute.p2.export.P2Exporter
+
If this plugin is present in your workspace, a project can define an export instruction. The export instruction +looks as follows:
+ +-export \
+ release.bndrun; \
+ type=p2; \
+ name=MyReleaseRepo.jar
+
With this instruction, the plugin exports a p2 repository named “MyReleaseRepo.jar” containing the specified features, plugins, and metadata. You can also include additional requirements and customize various aspects of the repository using Bnd’s capabilities. The key is the master release bndrun file, the specified type is for a p2 repository.
+ +In the release.bndrun
file you can specify the following aspects:
A list of features that needs to be included in this release under the macro features
:
features = \
+ bndtools.main.feature.bndrun, \
+ bndtools.m2e.feature.bndrun, \
+ bndtools.pde.feature.bndrun The list must point to the paths of bndrun files. Each bndrun file will contain specific information for the feature. The bndrun files inherit from the `release.bndrun` file and then from the workspace.
+
The update URL and label can also be specified with macros:
+ +update = https://bndtools.jfrog.io/bndtools/update-latest
+update.label = Bndtools Update Site
+
Each feature can participate in a number of categories. The category definitions look as follows:
+ +-categories \
+ bndtools; \
+ label = Bndtools; \
+ description = Bndtools, the incredible IDE for OSGi
+
This can be repeated by adding more clauses. A feature’s bndrun file must set the Bundle-Category that is then matched in this list fo the label and description.
+ +And last but not least, general header files.
+ +Bundle-Version 7.0.0
+Bundle-License: \
+ ASL-2.0;\
+ description="This program and the accompanying materials are made available under the terms of the Apache License, Version 2.0";\
+ link="https://opensource.org/licenses/Apache-2.0", \
+ EPL-2.0;\
+ description="This program and the accompanying materials are made available under the terms of the Eclipse License, Version 2.0";\
+ link="https://opensource.org/licenses/EPL-2.0"
+
+
+Bundle-DocURL: https://bnd.bndtools.org/
+Bundle-Vendor bnd
+Bundle-Copyright: Copyright bndtools
+
These headers are inherited in the features and can be overridden.
+ +Please note that the features you specify must be valid bndrun/feature files. The bndrun files inherit from the Workspace. It is therefore possible to set shared values in the workspace and use them in the bndrun files. For example, you can set the Bundle-Version
in the workspace and use it in all the feature bndrun files.
Although the features are not bundles, the exporter treats them as such. This means that most of the OSGi headers used for bundles can also be used for features. For example, you can set the Bundle-License
header in the manifest file of a feature to specify licensing information for that feature.
The following headers can be used in the feature files.
+ +Bundle-Name
– Human readable name of the featureBundle-SymbolicName
– Used for the feature id. This is default the same as the name of the feature/bndrun file.Bundle-Description
– A human readable description of the featureBundle-DocUrl
– A URL to the documentation of the featureBundle-Copyright
– The copyrightBundle-License
– The licensesBundle-Category
– The category of the featureBundle-Vendor
– The vendor will be used as the provider of the featureThe format of these headers is exactly as outlined by their OSGi specification.
+ +Features can require bundles and other features.
+ +Bundles will come from the -runbundles
instruction. This instruction can be managed with the resolver but it can also be set manually. Standard rules apply. The workspace is consulted to find the bundles and their versions. The bundles will be the highest available version in the repository.
-runbundles \
+ biz.aQute.bnd.annotation;version=5.0.0,\
+ org.apache.felix.gogo.runtime;version=1.1.0
+
Features can also require other features. Features, and theoretically other requirements can be added using the Require-Capability header. For this reason, a number of pseudo namespaces are created to make it more readable.
+ +feature
– Specifies a featureosgi.bundle
specifies a bundle requirement and java.package
specifies a package requirement. This way, any P2 eclipse namespace can be added.These pseudo namespaces use the attributes name
and version
to specify the name and version range of the requirement. They do not require a filter since p2 lacks this concept.
The following example shows how to specify a feature requirement:
+ +Require-Capability: \
+ feature; \
+ name= com.example.my.first.feature; \
+ version='[1.1.0,1.1.0]'
+
If no version is specified in a feature, it will default to the version of the requiring feature. This means that if you specify a feature requirement without a version, the version of the requiring feature will be used as the version requirement for the required feature.
+ +Features can be grouped in categories.A feature can be a member in a category by specifying the Bundle-Category
header. The value of this header is the identity of the category. For example, the following header specifies that the feature belongs to the category “bndtools” and “java”:
Bundle-Category: bndtools, java
+
The release bndrun file provide the details of the category. The following example shows how to specify the details of the categories can be specified with the -categories
instruction in the project’s bnd.bnd file:
-categories \
+ bndtools; \
+ label=Bndtools; \
+ description=Bndtools is a set of tools for OSGi development,\
+ java; \
+ label=Java ©; \
+ description=The Java language
+
The output will be a self contained p2 repository contained in a JAR file. it contains the following:
+ +content.jar
– This is the metadata of the repositoryartifacts.jar
– A list of all the artifacts in the repository and rules how to map an id to a file in the p2 archive.plugins/
– A directory with all the bundles in the repositoryfeatures/
– A directory with all the features in the repositoryThe repository can be used as a drop-in replacement for the Eclipse p2 repository. It can be used to update an Eclipse installation or to install a new Eclipse installation.
+ +In the workspace’s build.bnd file:
+ +-plugin \
+ aQute.p2.export.P2Exporter
+
+Bundle-Version: 1.0.0
+Bundle-Vendor: Example
+Bundle-License: Apache-2.0
+Bundle-Copyright: Public domain
+
In project X’s bnd.bnd file:
+ +-export \
+ release.bndrun; \
+ name=myrepo.jar; \
+ features="a.bndrun,b.bndrun"
+
+-categories \
+ java; \
+ label=Java ©; \
+ description=The Java language
+
In the a.bndrun file:
+ +Bundle-Name: A
+Bundle-SymbolicName: com.example.a
+Bundle-Description: A feature called A!
+Bundle-Category: java
+Bundle-DocUrl: https://example.com/a
+
+Require-Capability: \
+ feature; \
+ name= com.example.b
+
+-runbundles: \
+ com.example.util, \
+ com.example.a
+
In the b.bndrun file:
+ +Bundle-Name: B
+Bundle-SymbolicName: com.example.b
+Bundle-Description: A feature called B!
+Bundle-Category: java
+Bundle-DocUrl: https://example.com/b
+
+-runbundles: \
+ com.example.util, \
+ com.example.util2
+ com.example.b
+
This will generate a p2 archive called myrepo.jar with:
+ +content.jar
+ content.xml
– The metadata of the repository
+ artifacts.jar
+ artifacts.xml
– A list of all the artifacts in the repository and rules how to map an id to a file in the p2 archive.plugins/
+ com.example.util-1.0.0.jar
com.example.util2-1.0.0.jar
com.example.a-1.0.0.jar
com.example.b-1.0.0.jar
features/
+ a-1.0.0.jar
+ feature.xml
– The feature metadata for feature ab-1.0.0.jar
+ feature.xml
– The feature metadata for feature bP2 is a surprisingly complex beast for what it tries to achieve. The P2Exporter plugin makes an attempt to map the much cleaner OSGi model to the P2 model. Sadly, P2 has the attitude that the file formats are not standardized and that the only way to get the right format is to use the P2 API. The authors found it hard to understand the concept of backward compatibility. Once a format is out there, it must be supported in a backward compatible way forever, severely restricting the ability to change the data format. Which means that most of the data formats have been reverse enginered from existing repos. This is not a good situation. Where the OSGi provides a solid foundation with well defined namespaces, the P2 model seems a lot more ad-hoc.
+ +To minimize compatibility problems for this bnd plugin, the code generates the metadata in almost exactly the way it was found that p2 generated the metadata for bndtools. Writing this code raised lots of questions but there was nobody to ask as far as I could see. Any feedback would be highly appreciated.
+ +This is a read only Repository, that enables bnd to get dependencies from a P2 Repository. As bnd does not know the concept of Eclipse Features or Directory shaped bundles, it will not recognize such artifacts.
+ +As P2 does not support all the necessary OSGi metadata, bnd will download the whole content of the repository, so it can analyze it and build its own index. So be cautious, when referencing large repositories.
+ +The class name of the plugin is aQute.bnd.repository.p2.provider.P2Repository
.
It can take the following configuration properties:
+ +Property | +Type | +Default | +Description | +
---|---|---|---|
name |
+ NAME |
+ p2 + url |
+ The name of the repository. | +
url |
+ URI |
+ + | The URL to either the P2 repository (a directory) or an Eclipse target platform definition file. | +
location |
+ STRING |
+ + | The location to store the index file and where bundles will be downloaded to. | +
-plugin.p2: \
+ aQute.bnd.repository.p2.provider.P2Repository; \
+ url = https://download.eclipse.org/modeling/emf/emf/builds/release/2.21; \
+ name = EMF
+
A Maven POM can be viewed as the root node in an artifact transitive dependency graph. The Bnd Pom Repository plugin reads this graph and provides the set of artifacts as a bnd repository. The purpose of this plugin is to be able to have a single dependency definition that can be used by Maven projects and bnd projects.
+ +The pom can be a file on the local file system, a URL, a group, artifact, version (GAV) coordinate, or a query expression on maven central.
+ +One can express dependencies in a pom.xml
file:
<dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.enroute.base.api</artifactId>
+ <version>2.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.enroute.pom.distro</artifactId>
+ <version>2.0.0</version>
+ </dependency>
+</dependencies>
+
In a bndrun file in the same directory we can now use this pom.xml
file as our repository:
-standalone: true
+-plugin.enroute-distro = \
+ aQute.bnd.repository.maven.pom.provider.BndPomRepository; \
+ snapshotUrl=https://oss.sonatype.org/content/repositories/osgi/; \
+ releaseUrl=https://repo.maven.apache.org/maven2/; \
+ pom=${.}/pom.xml; \
+ name=enRouteDistroPom
+
Opening the Run
tab of the bndrun editor on this file will show you all transitive dependencies.
Maven Central supports a searching facility based on Solr. For example, you want all the artifacts of a given group id. In that case you could use the following Bnd Pom Repository plugin:
+ +-standalone: true
+-plugin.query = \
+ aQute.bnd.repository.maven.pom.provider.BndPomRepository; \
+ releaseUrl=https://repo.maven.apache.org/maven2; \
+ query='q=g:%22biz.aQute.bnd%22'; \
+ name=Query
+
The query must return a JSON response.
+ +Property | +Type | +Default | +Description | +
---|---|---|---|
releaseUrl |
+ URI... |
+ + | Comma separated list of URLs to the repositories of released artifacts. | +
snapshotUrl |
+ URI... |
+ + | Comma separated list of URLs to the repositories of snapshot artifacts. | +
+ | + | + | If this is not specified, it falls back to the release repository or just local if that is also not specified. |
+
local |
+ PATH |
+ ~/.m2/repository |
+ The file path to the local Maven repository. | +
+ | + | + | If specified, it should use forward slashes. If the directory does not exist, the plugin will attempt to create it. | +
+ | + | + | The default can be overridden with the maven.repo.local System property. |
+
revision |
+ GAV... |
+ + | A comma separated list of Maven coordinates. The GAV will be searched in the normal way. For further information about Coordinates & Terminology please see MavenBndRepositoryPlugin | +
pom |
+ URI... |
+ + | A comma separated list of URLs to POM files. | +
location |
+ PATH |
+ cnf/cache/pom-<name>.xml |
+ Optional cached index of the parsed POMs. | +
query |
+ STRING |
+ + | A Solr query string. This is the part after ? and must be properly URL encoded |
+
queryUrl |
+ URI |
+ https://search.maven.org/solrsearch/select |
+ Optional URL to the search engine. | +
name |
+ STRING |
+ + | Required name of the repo. | +
transitive |
+ true|false |
+ true |
+ If set to truthy then dependencies are transitive. | +
poll.time |
+ integer |
+ 5 minutes | +Number of seconds between checks for changes to POM files referenced by pom or revision . If the value is negative or the workspace is in batch/CI mode, then no polling takes place. |
+
dependencyManagement |
+ boolean |
+ false | +If set to true , dependencies in the dependencyManagement section will be handled as actual dependencies. |
+
One, and only one, of the pom
, revision
, or query
configurations can be set. If multiple are set then the first in [pom, revision, query]
is used and the remainders are ignored.
The Maven Bnd Repository uses the bnd Http Client. See the -connection-settings instruction for how to set the proxy and authentication information.
+ +If you use a remote repository then you must configure the credentials. This is described in -connection-settings. Placing the following XML in ~/.bnd/settings.xml
will provide you with the default Nexus credentials:
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
+ http://maven.apache.org/xsd/settings-1.0.0.xsd">
+ <servers>
+ <server>
+ <id>http://localhost:8081</id>
+ <username>admin</username>
+ <password>admin123</password>
+ </server>
+ </servers>
+</settings>
+
Notice that the id must match the scheme, the host, and the port if not the default port for the scheme.
+ +The repository view in the IDE will show detailed information when you hover the mouse over the the repository entry, the program entry, or the revision entry.
+ +releaseUrl
and snapshotUrl
configuration parameters. This is for security reasons.-runblacklist
instruction.biz.aQute.bnd:biz.aQute.bnd.runtime.snapshot:5.1.0
+
.
+ Choose or drag your snapshot.json file here.
+ + + + +The Snapshot Viewer can take a JSON-File which was created using the bnd Snapshot
+ Bundle (biz.aQute.bnd:biz.aQute.bnd.runtime.snapshot:5.2.0
).
+ It will create a snapshot of the framework when
+ the framework stops (stop 0
) or with a gogo command bnd:snapshot
to make an
+ arbitrary snapshot.
+
The snapshot can be used to get an extensive overview of the state of the framework. It captures the + framework (bundle,packages,services), the SCR info, configuration, log, and custom extensions. + An html file is provided to view the snapshot, which is this page you are looking at.
+If you're running snapshot inside the a Launchpad test case, the snapshot file will be named after the + actual test case
+You can add additional information providers by registering any services that can turned into JSON + as a service with the `snapshot` property.
+ + + +
+ Run bnd:snapshot
in your OSGi console to create the Snapshot JSON file.
+
osgi> bnd:snapshot
+Name snapshot-20210210213618.json
+Parent null
+CanonicalPath /mybndworkspace/plugins/com.myappbundle/snapshot-20210210213618.json
+ParentFile null
+Path snapshot-20210210213618.json
+AbsolutePath /mybndworkspace/plugins/com.myappbundle/snapshot-20210210213618.json
+AbsoluteFile /mybndworkspace/plugins/com.myappbundle/snapshot-20210210213618.json
+CanonicalFile /mybndworkspace/plugins/com.myappbundle/snapshot-20210210213618.json
+
+
+
+ + Take this .json file, open this page in your browser + and upload the file. +
+ ++ | Id | +Symbolic Name | +Version | +State | +Last Modified | +|||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
+ |
+ {{bundle.id||0}} | +
+ {{bundle.symbolicName}}
+ {{bundle.headers['Bundle-Description']}} + |
+ {{bundle.version}} | ++ + | +
+ |
+ |||||||||||||||||||||||||||||||||||
+ |
+
|
+
+ | Id | +Interface(s) | +Timing(s) | +Scope | +Component | +Registrar | +Using Bundles | + + + +
---|---|---|---|---|---|---|---|
+ |
+ + {{service.id}} + | +
+ {{objectClass(service.properties.objectClass)}}
+ {{service.properties['service.description']}} + + |
+
+ |
+ + {{service.properties['service.scope']}} + | +
+ |
+
+ |
+
+ |
+
+ |
+ |
+
+ | Id | +Timing | +Template | +State | +Service | +Bundle | +References | + + + +||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
+ |
+ {{instance.id}} | +
+ |
+
+ |
+ + {{getComponentState(instance)}} + | +
+ |
+
+ |
+
+
+ {{ref.name}} (
+
+
+ |
+ ||||||||||||||||||||||||||||||||
+ |
+
|
+
+ | Id | +Service(s) | +Bundle | +Enabled | +Instances | + + + +||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
+ |
+
+ {{template.id}}
+ |
+ + {{objectClass(template.serviceInterfaces)}} + | +
+ |
+ + {{template.isEnabled}} + | +
+ |
+ ||||||||||||||||||||||||||||||||||||||||||
+ + | +
+
|
+
Id | +Package | +Version | +Exporter | +Importers | +Duplicates | +Removal Pend. | + + +
---|---|---|---|---|---|---|
{{p.id}} | +{{p.name}} | +
+ {{v}}
+ |
+
+ |
+
+ |
+
+ |
+ {{p.removalPending}} | +
+ | FactoryPID | +PID | +Change | +Location | + + + +
---|---|---|---|---|
+ |
+ + {{cnf.factoryPid}} + | ++ {{cnf.pid}} + | ++ {{cnf.changeCount}} + | ++ {{cnf.bundleLocation}} + | +
+ |
+ |
+
Seq | +Level | +Message | +Time | +Name | +Bundle | +Thread | + + +
---|---|---|---|---|---|---|
{{log.sequence}} | +{{getLogLevel(log)}} | +
+ {{log.message}}
+ {{log.exception}}
+ |
+ {{new Date(log.time)}} | +{{log.loggerName}} | +
+ |
+ {{log.threadInfo}} | +
Last modified
+
Link | Version | doc/src | Description | Bytes |
---|
See the documentation on GitHub for details on how to configure and +use the Bnd Gradle plugins.
+ + +See the documentation on GitHub for details on how to configure and +use the Bnd Maven Plugins.
+ + +See the Bndtools page for details.
+ + +One set of developers comes from the Maven world and is generally happy with the “Maven Way”; they do not want it to change significantly. We feel that these users are already well served by the Apache Felix Maven Bundle Plugin, m2eclipse and even other IDEs such as NetBeans. We call the above a “Maven first” approach. bndlib supports this approach with the Apache Felix Maven Bundle plugin. This plugin is maintained by the Apache Felix project the plugin is well documented there. This document shamelessly copies some of this information.
+ +The Apache Felix Maven Bundle plugin uses bndlib only to create a manifest; it does not support the bndlib’s workspace and project model. This means that not all instructions and macros are relevant.
+ +The Apache Felix Maven Bundle plugin maps the bndlib instructions to XML elements in the POM in the configuration part of the plugin, bndlib is then called to create the manifest for the JAR. Before the Apache Felix Maven Bundle Plugin calls bndlib, it sets up the class path and provides a number of defaults that differ from the standard bndlib defaults. These changes will be discussed later.
+ +Rather than going straight to a detailed list of plugin features, we will first look at a simple example of how to use the plugin to give an immediate flavor. A detailed “how to” will follow. Assume that we have a simple bundle project that has a pubic API package an several implementation packages, such as:
+ +com.acme.prime.speaker.api
+com.acme.prime.speaker.provider
+com.acme.prime.speaker.provider.mac
+com.acme.prime.speaker.provider.unix
+com.acme.prime.speaker.provider.windows
+
If we also assume that we have a bundle activator in one of the implementation packages, then the <plugins>
section of the POM file for this bundle project would look like this:
<plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>com.acme.prime.speaker.api</Export-Package>
+ <Private-Package>com.acme.prime.speaker.provider.*</Private-Package>
+ <_dsannotations>*</_dsannotations>
+ </instructions>
+ </configuration>
+ </plugin>
+
It should be clear that the Apache Felix Maven Bundle plugin uses the XML tag names in the configuration for the headers and instructions. Since the name of most instructions start with a minus sign (‘-‘), which is not allowed in XML, the first minus sign is replaced with an underscore (‘_’). Most instructions and macros can be used except for the project/workspace instructions since these concepts are available from Maven. Such project instructions and macros are indicated on their reference pages.
+ +As always in bndlib, any element starting with an upper case us copied to the manifest, all other elements are internal to bnd; they are available as macros and might indicate instructions to bnd (which mostly start with a minus sign).
+ +You can use macros but you must be careful to avoid conflicting with the macros used in the POM that also use the ${...}
pattern; it is therefore necessary for bnd to use one of its alternative patterns, for example $[...]
. The maven POM macros are fortunately already expanded before bndlib sees them.
Since all elements become part of the bndlib properties you can actually also set macros for later use:
+ + <instructions>
+ <opt>resolution:=optional</opt>
+ <Import-Package>org.slf4j;$[opt], *</Import-Package>
+ </instructions>
+
The Apache Felix Maven Bundle plugin supports a special instruction
++ +This is a convenient feature that is not so wise to use since it makes the actual bundle depending on many aspects that are not under direct control. A bundle is a component and should reflect an implementation of a public API; this model requires that you think about what goes in there and what does not go in there. Adding transitive dependencies inside this bundle tends to create very complex systems that destroy the benefits of OSGi.
+
To use this plugin, very little information is required by bndlib. As part of the Maven integration, the plugin tries to set reasonable defaults based on the POM for various instructions.
+ +Default Headers:
+ + Bundle-SymbolicName: Bundle-Name: project.getName();
+ Bundle-Version: normalized pom <version>
+ Import-Package: *
+ Export-Package: all packages except packages with impl/internal in their name
+ Bundle-Description: project.getDescription()
+ Bundle-License: project.getLicenses())
+ Bundle-Vendor: project.getOrganization().getName();
+ Bundle-DocURL: project.getOrganization().getUrl()
+ Include-Resource: src/main/resources
+
Special Macros:
+ +${bundle-symbolicname}
+${local-packages}
+
The
The computed symbolic name is also stored in the $[maven-symbolicname] property in case you want to add attributes or directives to it.
+ +Export-Package is assumed to be the set of packages in your local Java sources, excluding the default package ‘.’ and any packages containing ‘impl’ or ‘internal’. (before version 2 of the bundleplugin it was based on the symbolic name)
+ +++ +From a bndlib perspective, this is the wrong default. An exported package is an expensive thing to have and the philosophy of bndlib is to make these expensive choices explicit, this is the reason the bndlib default is to make nothing exported, especially because good bundles should have no or very few exports.
+
To use the maven-bundle-plugin, you first need to add the plugin and some appropriate plugin configuration to your bundle project’s POM. Below is an example of a simple OSGi bundle POM for Maven2:
+ +<project>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>my-osgi-bundles</groupId>
+ <artifactId>examplebundle</artifactId>
+ <packaging>bundle</packaging> <!-- (1) -->
+ <version>1.0</version>
+ <name>Example Bundle</name>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin> <!-- (2) START -->
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>com.my.company.api</Export-Package>
+ <Private-Package>com.my.company.*</Private-Package>
+ <Bundle-Activator>com.my.company.Activator</Bundle-Activator>
+ </instructions>
+ </configuration>
+ </plugin> <!-- (2) END -->
+ </plugins>
+ </build>
+</project>
+
There are two main things to note: (1) the
Consider this more real-world example using Felix’ Log Service implementation. The Log Service project is comprised of a single package: org.apache.felix.log.impl. It has a dependency on the core OSGi interfaces as well as a dependency on the compendium OSGi interfaces for the specific log service interfaces. The following is its POM file:
+ +<project>
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.log</artifactId>
+ <packaging>bundle</packaging>
+ <name>Apache Felix Log Service</name>
+ <version>0.8.0-SNAPSHOT</version>
+ <description>
+ This bundle provides an implementation of the OSGi R4 Log service.
+ </description>
+ <dependencies>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>0.8.0-incubator</version>
+ </dependency>
+ <dependency>
+ <groupId>${pom.groupId}</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>0.9.0-incubator-SNAPSHOT</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Export-Package>org.osgi.service.log</Export-Package>
+ <Private-Package>org.apache.felix.log.impl</Private-Package>
+ <Bundle-SymbolicName>${pom.artifactId}</Bundle-SymbolicName>
+ <Bundle-Activator>${pom.artifactId}.impl.Activator</Bundle-Activator>
+ <Export-Service>org.osgi.service.log.LogService,org.osgi.service.log.LogReaderService</Export-Service>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+
This will create a manifest of:
+ +Manifest-Version: 1
+Bundle-License: http://www.apache.org/licenses/LICENSE-2.0.txt
+Bundle-Activator: org.apache.felix.log.impl.Activator
+Import-Package: org.osgi.framework;version=1.3, org.osgi.service.log;v
+ ersion=1.3
+Include-Resource: src/main/resources
+Export-Package: org.osgi.service.log;uses:=org.osgi.framework;version=
+ 1.3
+Bundle-Version: 0.8.0.SNAPSHOT
+Bundle-Name: Apache Felix Log Service
+Bundle-Description: This bundle provides an implementation of the OSGi
+ R4 Log service.
+Private-Package: org.apache.felix.log.impl
+Bundle-ManifestVersion: 2
+Export-Service: org.osgi.service.log.LogService,org.osgi.service.log.L
+ ogReaderService
+Bundle-SymbolicName: org.apache.felix.log
+The resulting bundle JAR file has the following content (notice how the LICENSE and NOTICE files were automatically copied from the src/main/resources/ directory of the project):
+
If you want to keep your project packaging type (for example “jar”) but would like to add OSGi metadata +you can use the manifest goal to generate a bundle manifest. The maven-jar-plugin can then be used to +add this manifest to the final artifact. For example:
+ +<plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+</plugin>
+<plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+</plugin>
+
If you want to use packaging types other than “jar” and “bundle” then you also need to enable support +for them in the bundleplugin configuration, for example if you want to use the plugin with WAR files:
+ +<plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>bundle-manifest</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>manifest</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <supportedProjectTypes>
+ <supportedProjectType>jar</supportedProjectType>
+ <supportedProjectType>bundle</supportedProjectType>
+ <supportedProjectType>war</supportedProjectType>
+ </supportedProjectTypes>
+ <instructions>
+ <!-- ...etc... -->
+ </instructions>
+ </configuration>
+</plugin>
+
You’ll also need to configure the other plugin to pick up and use the generated manifest, which is written to ${project.build.outputDirectory}/META-INF/MANIFEST.MF by default (unless you choose a different manifestLocation in the maven-bundle-plugin configuration). Continuing with our WAR example:
+ +<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-war-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+ </archive>
+ </configuration>
+</plugin>
+
Though the Apache Felix Maven Bundle plugin is used in a ‘maven first’ model, it can actually also be used with some of the benefits of using bndlib more direct so that for example bndtools can be used. In this model, the POM includes a bnd file that is also used by bnd(tools). This is far from perfect since it will be necessary to maintain the classpath in two different places: through the transitive dependencies in the POM and in bnd. It will also not provide full fidelity between the maven build and the IDE build since other plugins in maven are ignored. However, it will allow the use of bndtools as the primary IDE, which allows you to develop bundles faster than any other environment.
+ +If you want to use this model, be aware that bnd has a strict disk layout for the workspace. A workspace is a directory with a cnf
directory and project directory. It is not possible to create arbitrary layouts. For maven, the disk layout should be:
./com.acme.prime/ workspace
+ cnf/ configuration
+ ext/ extensions
+ maven.bnd contains the maven plugin setup
+ build.bnd your shared settings
+ pom.xml maven modules POM
+ com.acme.prime.speaker.api/ project directory
+ pom.xml your project POM
+ src/ maven source directory
+ main/
+ java/
+ test/
+ java/
+ target/
+ classes/ output directory
+ test-classes/ test output
+
You can include files with the -include instruction:
+ + <instructions>
+ <_include>bnd.bnd</_include>
+ </instructions>
+
Now you can add the instruction in the bnd.bnd
file:
Export-Package: com.acme.prime.speaker.api
+Private-Package: com.acme.prime.speaker.provider.*
+-dsannotations: true
+
The easiest way to setup such a workspace is is by adding the bnd maven plugin:
+ +$ bnd add workspace com.acme.prime
+$ cd com.acme.prime
+$ bnd add plugin maven
+
The bnd maven plugin will automatically maintain a pom.xml in the workspace containing the workspace projects.
+ +$ bnd add project com.acme.speaker.api
+$ more pom.xml
+<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>/Users/aqute/tmp/com.acme.prime</groupId>
+ <artifactId>root</artifactId>
+ <packaging>pom</packaging>
+
+ <modules>
+ <!-- DO NOT EDIT MANAGED BY BND MAVEN LIFECYCLE PLUGIN -->
+ <module>com.acme.prime.speaker.api</module>
+ </modules>
+</project>
+
You can edit this file to add more specific information, the bnd maven plugin will only change the marked part.
+ +The maven bnd plugin also changes the file layout to match the default maven file layout. Take a look at cnf/ext/maven.bnd
for the details.
See the Gradle Bundle Plugin page for details.
+ + +See the Gradle User Guide page for details.
+ + +Plugin to build OSGi bundles with the mill build tool.
+ +// mill default imports
+import mill._, scalalib._
+// Load mill-osgi in version 0.2.0
+import $ivy.`de.tototec::de.tobiasroeser.mill.osgi:0.2.0`
+// and import its main package
+import de.tobiasroeser.mill.osgi._
+
+object project extends JavaModule with OsgiBundleModule {
+
+ def bundleSymbolicName = "com.example.project"
+
+ def osgiHeaders = T{ osgiHeaders().copy(
+ `Export-Package` = Seq("com.example.api"),
+ `Bundle-Activator` = Some("com.example.internal.Activator")
+ )}
+
+}
+
Please refer the plugin project page for the full documentation and download details.
+ + +This Gradle Plugin creates an OSGi runtime based on the project’s dependencies, +which may include the project itself as well as its subprojects, and can run it with +Apache Felix, Equinox or Knopflerfish.
+ +See the osgi-run page on GitHub for details.
+ + +