Skip to content

Repository files navigation

base-multi-versions

Multi FHIR version sources for the base / core HL7 FHIR IG.

This repository holds the source files for the base / core HL7 FHIR IG. More information on the IG, meeting minutes and telco details can be found on HL7 confluence.

The current builds of the implementation guide can be found at the following URLs:

Feedback on the specification is tracked using HL7 Jira.

Multi-version approach

This repository contains the source files to build and deploy a multi-FHIR version of the HL7 Europe Base and Core FHIR IG.

┌──────────────────────────────────────┐
│                                      │
│ github.com/hl7-eu/base-multi-versions│
│                                      │
└───────────────┬──────────────────────┘
                │
                │       ┌──────────────────────────┐
                ├───────► github.com/hl7-eu/base   │
                │       └──────────────────────────┘
                │
                │       ┌─────────────────────────────┐
                └───────► github.com/hl7-eu/base-r5   │
                        └─────────────────────────────┘

The content of this repository is processed and generates the content of the base and base-r5 repositories. The content of these repositories is not edited directly but generated based on the content of this repository.

This approach has been chosen as it allows both versions of the specification to be generated from a single codebase and still allows the R4 and R5 versions to be build using the FHIR autobuilder.

The version-specific IGs are generated by a preprocessing step: the sources are copied from ig-src to one directory per FHIR version, and files whose name contains .liquid. are rendered with LiquidJS, using the variables from the context-<Rx>.json file of that version.

   ig-src/  +  context-R4.json   ──preprocessing──►   igs/base-r4/   ──IG publisher──►   igs/base-r4/output/
   ig-src/  +  context-R5.json   ──preprocessing──►   igs/base-r5/   ──IG publisher──►   igs/base-r5/output/

Edit the sources in ig-src only. The igs/base-r4 and igs/base-r5 directories are generated output: every preprocessing run deletes and recreates their content, so changes made there are lost.

Prerequisites

Tool Used for
Java 11 or later running the IG publisher
Node.js (provides npx) rendering the liquid templates during preprocessing
SUSHI (npm install -g fsh-sushi) compiling the FSH files
Jekyll generating the IG pages
curl, bash the scripts in this repository (or PowerShell — see below)

The IG publisher itself is not part of the repository, it is downloaded on demand — see Updating the build tooling.

Every .sh script that builds the IGs has a .ps1 equivalent, for building on Windows with PowerShell instead of bash — e.g. _preProcessAndCheckAll.ps1 alongside _preProcessAndCheckAll.sh. Both take the same arguments and behave the same way; the examples below use the .sh form but can be replaced with .ps1.

Quick start

# 1. edit the sources in ig-src/

# 2. generate both FHIR versions and build them with the IG publisher
./_preProcessAndCheckAll.sh
# on Windows: ./_preProcessAndCheckAll.ps1

# 3. open the results
#    igs/base-r4/output/index.html
#    igs/base-r5/output/index.html

# 4. once the result is fine, commit and push — CI builds both versions again
#    and publishes them to the base and base-r5 repositories
git push

A full build of both versions takes a while. To work on a single FHIR version, pass its version number: ./_preProcessAndCheckAll.sh 4.0.1 or ./_preProcessAndCheckAll.sh 5.0.0 (.ps1 on Windows).

Repository structure

Path Description
ig-src/ The IG sources — this is where all edits are made. Contains input/ (FSH files, page content, images), the ig-template, and the liquid templates for sushi-config.yaml, ig.ini and publication-request.json. Everything the base and base-r5 repositories should contain lives here, including their .gitignore, LICENSE and README.liquid.md.
context-R4.json, context-R5.json The variables used to render the liquid templates, one file per FHIR version.
igs/base-r4/, igs/base-r5/ Generated, version-specific IGs, including the build results in their output/ directory. Not edited by hand.
igs/publisher.jar The IG publisher, shared by all generated IGs. Downloaded on demand.

Scripts

Script Description
_preProcessAndCheckAll.sh / .ps1 [4.0.1|5.0.0] Preprocessing plus a full IG publisher build of the generated IGs. The usual entry point.
_preprocessMultiVersion.sh / .ps1 [4.0.1|5.0.0] Preprocessing only: generates igs/base-r4 and igs/base-r5 from ig-src.
_updateBuildTools.sh / .ps1 [scripts|publisher] Downloads the IG publisher and the HL7 build scripts.
igs/base-<rx>/_build.sh / _build.bat The HL7 build script, used to build a single generated IG.

Without an argument, the scripts that take a FHIR version process both versions. The .ps1 scripts are PowerShell equivalents of the .sh ones, for use on Windows; _build.bat is the Windows counterpart of _build.sh and is called by the .ps1 scripts instead.

Writing version-specific content

Any file in ig-src whose name contains .liquid. is rendered during preprocessing and written without the .liquid part — sushi-config.liquid.yaml becomes sushi-config.yaml, condition-eu-core.liquid.fsh becomes condition-eu-core.fsh. Files without .liquid. in their name are copied unchanged, so only files that actually differ between FHIR versions need to be templates.

The variables available in the templates are defined in context-R4.json and context-R5.json:

Variable R4 R5
isR4 / isR5 true / false false / true
fhirVersion 4.0.1 5.0.0
r-code / R-code r4 / R4 r5 / R5
eu-core-r-code "" -r5
R4 / R5 "" / //R5 //R4 / ""
R4-yaml / R5-yaml "" / # R5 # R4 / ""
R (with a trailing space) "" ""

The empty cells of the -yaml row are not defined at all in that context file. An unknown variable renders as an empty string, which is what the comment markers rely on.

There are two main patterns to write version-specific content.

The first pattern uses the {% if isR4 %} and {% endif %} statements as is depicted in the example below.

* status = #final
{% if isR5 %}
* version = "1.0.0" // invented - not there in the report
{% endif %}
{% if isR4 %}
* extension[version].valueString = "1.0.0"
{% endif %}

In the R4 version of the shorthand file only the R4 part will be present, the R5 version will only have the R5 alternative.

This approach has the advantage that indentation remains in place, but the disadvantage that line numbers change. An alternative approach is illustrated below.

* status = #final
{{R5}}* version = "1.0.0" // invented - not there in the report
{{R4}}* extension[version].valueString = "1.0.0"

In the R4 version, {{R4}} is replaced by "" and {{R5}} by "//R5". In the R5 version, {{R4}} is replaced by "//R4" and {{R5}} by "". This keeps the different sections clearly marked and preserves line numbers, at the cost of indentation alignment.

{{R }} buys that alignment back. Its key ends in a space and it renders as nothing in both versions, so it can carry the lines that belong to both through a block whose neighbours are marked:

{{R4}}* bodySite
{{R5}}* bodyStructure
{{R }}  * coding[+].system = "http://snomed.info/sct"
{{R }}  * coding[=].code = #48694002

Every line starts in the same column, so the nesting of the FSH stays readable while the markers are there.

{{R4-yaml}} and {{R5-yaml}} do the same for files in which // does not start a comment — ig.ini and sushi-config.yaml — by rendering as # instead:

{{R4-yaml}}ig = fsh-generated/resources/ImplementationGuide-hl7.fhir.eu.base.json
{{R5-yaml}}ig = fsh-generated/resources/ImplementationGuide-hl7.fhir.eu.base-r5.json

The main files on which this process is typically used are sushi-config.yaml, ig.ini and fsh files. FHIR release specific pages are generated using the standard variables made available by the IG-publisher, mainly site.data.fhir.version.

Jekyll variables in a template

Those publisher variables — site.data.fhir.path, site.data.fhir.version and the dozen or so data files behind site.data.* — are resolved by Jekyll when it renders the page. Jekyll uses Liquid for that, and so does the preprocessing, so a page that is also a template is rendered twice, by two different engines.

The first pass only knows the variables from context-<Rx>.json. It resolves everything else to an empty string, and the expression never reaches Jekyll:

in ig-src              [validating FHIR profiles]({{ site.data.fhir.path }}validation.html)
after preprocessing    [validating FHIR profiles](validation.html)

Nothing reports this — the link simply points at the wrong place. Wrap the expression in {% raw %} to hand it through the first pass untouched. That tag is consumed there, so Jekyll sees a plain expression and resolves it as usual:

[validating FHIR profiles]({% raw %}{{ site.data.fhir.path }}{% endraw %}validation.html)

This applies to files with .liquid. in their name only. Everything else is copied unchanged and reaches Jekyll as written.

Building the IGs

The simplest way is to run preprocessing and the IG publisher builds in one step. From the root directory, run:

./_preProcessAndCheckAll.sh          # both FHIR versions
./_preProcessAndCheckAll.sh 4.0.1    # only R4
./_preProcessAndCheckAll.sh 5.0.0    # only R5

On Windows, use _preProcessAndCheckAll.ps1 with the same arguments.

This will:

  1. Run ./_preprocessMultiVersion.sh to generate the version-specific IGs.
  2. Make sure a publisher.jar is available, downloading it to igs/publisher.jar if needed. All generated IGs share that single copy; a publisher.jar in the input-cache of an individual IG takes precedence over it.
  3. Build each IG by running ./_build.sh build in its directory.

The build results are written to igs/base-r4/output/ and igs/base-r5/output/; open the index.html in that directory to review them. Build errors and warnings are collected in qa.html.

Alternatively the steps can be run separately, which is useful when only one of them needs to be repeated:

  1. From the root directory, run:
    ./_preprocessMultiVersion.sh
    (_preprocessMultiVersion.ps1 on Windows.)
  2. The generated IGs will be found in the igs/base-r4 and igs/base-r5 directories.
  3. In each of those directories run ./_build.sh (_build.bat on Windows) to build the IG. The script offers a menu, or can be called directly with an argument:
    • ./_build.sh build – build the IG
    • ./_build.sh notx – build without a terminology server (faster, but no terminology validation)
    • ./_build.sh nosushi – build without running SUSHI
    • ./_build.sh clean – remove the output, template and temp directories

Updating the build tooling

Both the IG publisher and the HL7 build scripts are updated with:

./_updateBuildTools.sh             # both
./_updateBuildTools.sh scripts     # only _build.sh / _build.bat
./_updateBuildTools.sh publisher   # only publisher.jar

On Windows, use _updateBuildTools.ps1 with the same arguments.

  • publisher: downloads the latest publisher.jar from fhir-ig-publisher to igs/publisher.jar, the single copy shared by all generated IGs.
  • scripts: downloads _build.sh and _build.bat from ig-publisher-scripts into the ig-src directory. Run ./_preprocessMultiVersion.sh afterwards to propagate them to the generated IGs.

The update option built into _build.sh is deliberately not used for either: it is interactive, it always puts the jar in the input-cache of a single IG instead of the shared location, and updating the scripts in igs/base-r4 / igs/base-r5 has no effect, as those directories are overwritten on every preprocessing run.

Publishing to the base and base-r5 repositories

Publishing is done by CI rather than from a working copy: pushing a branch builds both FHIR versions and, if both succeed, syncs them to the base and base-r5 repositories — see Continuous integration below.

To publish again without changing anything, start the workflow by hand from the Actions tab of this repository (Validate and Deploy to Separate RepositoriesRun workflow), which its workflow_dispatch trigger allows.

Continuous integration

Every push runs .github/workflows/deploy-to-repos.yml, which uses the same scripts as a local build — see .github/workflows/README.md for details. It builds both FHIR versions with _preProcessAndCheckAll.sh, one job per version, and only if both succeed it syncs them to the base and base-r5 repositories, into a branch of the same name as the branch that was pushed.

A third job runs _preprocessMultiVersion.ps1 on a Windows runner and compiles the result with SUSHI, so that the PowerShell scripts do not drift away from their bash counterparts unnoticed. It does not run the IG publisher, and deployment does not depend on it.

Validation is deliberately not split into a second workflow: a pull request from a branch of this repository raises both a push and a pull_request event, which would build every IG twice.

About

Multi FHIR versions sources for the base / coer HL7 FHIR IG

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages