Julia and Gitlab self-hosted : a state-of-the-art?

@ArnaudHenry Did you try by any chance, to download an artifact using a token from your private Gitlab ?
I’ve been trying to configure package artifacts, but it seems the only way to connect is to use a token in the header of the request.

I would recommend to use Releases | GitLab (with a generic package) for artifacts, i.e., with a separate GitLab CI job in the GitLab CI pipeline to circumvent the limitation of Artifacts not supporting authentication - by downloading the artifacts to a local (cached) path before the Julia jobs needing the artifacts run (and re-writing the paths of the artifacts to use the local ones) - e.g. using GitForge.jl if possible - or python-gitlab.

I have yet to implement this, though - but I believe it should work :innocent: :slight_smile:

I would support implementing this in a common Julia GitLab CI Templates repo., e.g. IHP Systems / Julia / Julia GitLab CI templates · GitLab (which could be moved to a common JuliaCI org.) - the aim of which is to support generic Julia package development on GitLab CI, including:

  • Support for different project types, e.g., packages, applications, registries, BinaryBuilder recipes, packages with Python dependencies, (and packages using private artifacts).
  • Support for best practices, e.g. testing (with multi-threading), formatting with JuliaFormatter, and analysis with JET, and Aqua.
  • Support for GitLab CI features like test reports, caching, “needs”, etc.
  • Extended support for Julia versions (e.g. the last two LTS versions, and the last two stable versions) - to support CI even for projects migrating from older to newer versions, e.g. from Julia 1.0 to Julia 1.6.
  • Support for Julia-supported platfoms (across architectures and operating systems) - using tags compatible with GitLab.com SaaS runners (a super set of those runner tags).
  • Versioned and stable - respecting semantic versioning.
3 Likes

Thanks for your proposal, looking forward to seeing that in action !
In the meantime, I finally found a way to configure artifacts that works for my needs. See How to configure an artifact to use a header during package download? - #2 by BambOoxX

1 Like

Much of it is already there - but of course not the packages with private artifacts yet :slight_smile: Will update when I get to it.

If you are working with BinaryBuilder stuff I have also hacked some stuff together with sed to build, deploy locally and test in build_binaries and test_binaries in pylon_julia_wrapper/.gitlab-ci.yml at master · IHPSystems/pylon_julia_wrapper · GitHub (and mirrored in the corresponding GitHub Actions workflow).

@ArnaudHenry @oxinabox , something has been bothering me recently. It seems I can’t display coverage for each file of my repo in a merge request diff. I’m using LocalCoverage.jl to generate the coverage report as a Cobertura XML file. But I can’t figure if this file is properly used by the Gitlab.
Did you encounter such problems at Invenia ?

@BambOoxX I also have some packages on a self-hosted gitlab instance (not Invenia).
I do see the coverage of files that were changed within a merge request by uploading the XML generated using LocalCoverags.jl, but I can’t (or don’t know how) see the coverage of files that were not touched by the MR commits.

Is this the problem you are refering to, or you don’t see coverage at all?

@disberd Hmm I do not see both ^^. But first priority could be to see the coverage in the MR.
Could you share a samble of a Cobertura file as well as you .gitlab-ci file for coverage ?

@BambOoxX,

Here is a the cobertura sample

cobertura-coverage.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
<coverage branch-rate="0.0" branches-covered="0" branches-valid="0" complexity="0" line-rate="0.5056179775280899" lines-covered="45" lines-valid="89" timestamp="1689070463" version="2.0.3">
  <sources>
    <source>.</source>
  </sources>
  <packages>
    <package line-rate="0.5056179775280899" branch-rate="0.0" name="src" complexity="0">
      <classes>
        <class branch-rate="0.0" complexity="0" filename="src/ActiveAntenna.jl" line-rate="1.0" name="src.ActiveAntenna.jl">
          <methods/>
          <lines>
            <line branch="false" hits="6" number="1"/>
            <line branch="false" hits="1" number="34"/>
            <line branch="false" hits="1" number="36"/>
          </lines>
        </class>
        <class branch-rate="0.0" complexity="0" filename="src/basic_types.jl" line-rate="0.5211267605633803" name="src.basic_types.jl">
          <methods/>
          <lines>
            <line branch="false" hits="44" number="41"/>
            <line branch="false" hits="41857" number="50"/>
            <line branch="false" hits="0" number="52"/>
            <line branch="false" hits="0" number="53"/>
            <line branch="false" hits="0" number="54"/>
            <line branch="false" hits="0" number="55"/>
            <line branch="false" hits="0" number="56"/>
            <line branch="false" hits="0" number="57"/>
            <line branch="false" hits="0" number="58"/>
            <line branch="false" hits="0" number="59"/>
            <line branch="false" hits="0" number="61"/>
            <line branch="false" hits="0" number="63"/>
            <line branch="false" hits="0" number="64"/>
            <line branch="false" hits="0" number="65"/>
            <line branch="false" hits="9" number="73"/>
            <line branch="false" hits="7688" number="74"/>
            <line branch="false" hits="17882" number="93"/>
            <line branch="false" hits="17882" number="94"/>
            <line branch="false" hits="17882" number="95"/>
            <line branch="false" hits="16295" number="117"/>
            <line branch="false" hits="16295" number="118"/>
            <line branch="false" hits="16295" number="119"/>
            <line branch="false" hits="36" number="143"/>
            <line branch="false" hits="36" number="144"/>
            <line branch="false" hits="36" number="145"/>
            <line branch="false" hits="20" number="152"/>
            <line branch="false" hits="12" number="153"/>
            <line branch="false" hits="8" number="154"/>
            <line branch="false" hits="0" number="155"/>
            <line branch="false" hits="26" number="157"/>
            <line branch="false" hits="18" number="159"/>
            <line branch="false" hits="35" number="309"/>
            <line branch="false" hits="1" number="310"/>
            <line branch="false" hits="26" number="311"/>
            <line branch="false" hits="1" number="313"/>
            <line branch="false" hits="187" number="316"/>
            <line branch="false" hits="0" number="317"/>
            <line branch="false" hits="376" number="318"/>
            <line branch="false" hits="0" number="324"/>
            <line branch="false" hits="0" number="325"/>
            <line branch="false" hits="0" number="326"/>
            <line branch="false" hits="0" number="327"/>
            <line branch="false" hits="102" number="330"/>
            <line branch="false" hits="102" number="340"/>
            <line branch="false" hits="0" number="341"/>
            <line branch="false" hits="0" number="353"/>
            <line branch="false" hits="0" number="354"/>
            <line branch="false" hits="0" number="355"/>
            <line branch="false" hits="0" number="356"/>
            <line branch="false" hits="0" number="357"/>
            <line branch="false" hits="0" number="358"/>
            <line branch="false" hits="0" number="359"/>
            <line branch="false" hits="0" number="360"/>
            <line branch="false" hits="0" number="361"/>
            <line branch="false" hits="0" number="362"/>
            <line branch="false" hits="20" number="392"/>
            <line branch="false" hits="8" number="418"/>
            <line branch="false" hits="10" number="446"/>
            <line branch="false" hits="36" number="458"/>
            <line branch="false" hits="66" number="459"/>
            <line branch="false" hits="36" number="460"/>
            <line branch="false" hits="36" number="461"/>
            <line branch="false" hits="36" number="464"/>
            <line branch="false" hits="36" number="465"/>
            <line branch="false" hits="36" number="466"/>
            <line branch="false" hits="0" number="468"/>
            <line branch="false" hits="0" number="469"/>
            <line branch="false" hits="0" number="470"/>
            <line branch="false" hits="0" number="473"/>
            <line branch="false" hits="0" number="474"/>
            <line branch="false" hits="4" number="497"/>
          </lines>
        </class>
        <class branch-rate="0.0" complexity="0" filename="src/lattices.jl" line-rate="0.3333333333333333" name="src.lattices.jl">
          <methods/>
          <lines>
            <line branch="false" hits="102" number="6"/>
            <line branch="false" hits="41872" number="8"/>
            <line branch="false" hits="41872" number="10"/>
            <line branch="false" hits="51" number="12"/>
            <line branch="false" hits="51" number="13"/>
            <line branch="false" hits="0" number="32"/>
            <line branch="false" hits="0" number="33"/>
            <line branch="false" hits="0" number="34"/>
            <line branch="false" hits="0" number="36"/>
            <line branch="false" hits="0" number="39"/>
            <line branch="false" hits="0" number="40"/>
            <line branch="false" hits="0" number="41"/>
            <line branch="false" hits="0" number="56"/>
            <line branch="false" hits="0" number="69"/>
            <line branch="false" hits="0" number="84"/>
          </lines>
        </class>
      </classes>
    </package>
  </packages>
</coverage>

and here is the CI job I use:

CI job
.linux_test_coverage:
  rules:
    - !reference [.test_rules, rules]
  extends:
    - .linux_before
  coverage: /Test coverage (\d+\.\d+%)/
  script:
      # First we add registries and the LocalCoverage package in the main env
    - |
      julia -e '
        using Pkg
        Pkg.Registry.add("General")
        Pkg.add("LocalCoverage")'
      # Then we create the coverage report and percentage
    - |
      julia --project=@. -e '
        using Pkg
        Pkg.build()
        using LocalCoverage
        cov = generate_coverage()
        c, t = cov.lines_hit, cov.lines_tracked
        using Printf
        # This printing is needed to extract the coverage percentage from the pipeline to show on the README badge
        @printf "Test coverage %.2f%%\n" 100c / t
        show(cov) # This will print the coverage table on the job log
        write_lcov_to_xml("coverage/cobertura-coverage.xml", "coverage/lcov.info")'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

The .linux_before just sets up the image by installing git and setting up git credentials, while the .test_rules one just specifies rules for triggering the job.

You probably know that, but LocalCoverage only tracks files that are located in the src folder (and its subfolders), so if you have source code that is in another folder coverage for those is not computed.

Also by default the coverage file artifacts expires after 1 week if I am not mistaken, but should not expire while the MR is still open.

Here is then an example of the coverage visualization I get:

Thanks for that. As far as I can tell, there is not obvious difference between our Cobertura files (I actually implemented the native conversion to cobertura in LocalCoverage). I’ll fiddle a bit with the .gitlab-ci files since these are pretty different in terms of content…

@disberd I’m wondering, what is the structure of your repo ? Directly the julia project directory with

- src/
- Project.toml

?

Yes, all my projects have the package folder as root (so as you described above)

So that may explain why it does not work on my case… I’ll try to path my cobertura file as discussed in Cobertura coverage not showing in diff or jobs - #5 by Btripp1986 - GitLab CI/CD - GitLab Forum