2021

Introduksjon til emnet

Lysarkene gir en oversikt over organiseringen av emnet

Lysarkene gir en oversikt over git, gitlab og gitpod

Gitpod

Problemet som gitpod løser

Rigging av alt som trengs for utvikling tar mye tid. En skal installere språket (python, java, …​) og en trenger diverse rammeverk (junit, javafx, …​). Kanskje skal en bruke såkalte byggeverktøy (maven, gradle, …​) og det må installeres og konfigureres. På toppen av dette kommer selve IDE-en (VSCode, IntelliJ, Eclipse, …​), som må passe til og være konfigurert for det en har installert forøvrig.

En kompliserende faktor er versjoner, en må ha versjoner av alt som passer sammen og ofte trenger en ulike versjoner for ulike prosjekter. Selv om det finnes verktøy som sdkman som hjelper en å installere og veksle mellom ulike versjoner, så er det en del manuelt arbeid.

For større prosjekter er kanskje ikke dette merarbeidet så problematisk, det må jo ikke gjøres så ofte. Men hvis en veksler mellom flere mindre prosjekter eller bare skal jobbe med små eksempler og øvinger, så kan det bli veldig tungvint.

Gitpod = VSCode + git + Docker

Gitpod prøver å løse dette problemet ved å tilby en nettleserbasert IDE (VSCode), som er ferdigkonfigurert for spesifikke prosjekt. En slipper dermed å installere IDE-en. Konfigurasjonen av både IDE-en og den underliggende virtuelle maskina (Linux i Docker) ligger i git-kodelageret sammen med prosjektfilene forøvrig. Når kodelageret med konfigurasjon og prosjekfiler først er satt opp, så slipper en dermed også å installere språk, rammeverk og verktøy, det er bare å åpne IDE-en med utgangspunkt i kodelageret.

Jobben med oppsett må jo fortsatt gjøres, men en trenger bare å gjøre det én gang. Alle som siden skal jobbe med samme prosjekt, kan bare åpne IDE-en på et kodelager og gå i gang. Dersom det er snakk om et kode-eksempel, en øving eller et standardisert prosjekt, så kan det gjøres på forhånd av fagstaben, så i hvert fall studenten slipper styret med å sette alt opp. Og for nye prosjekt kan en ofte ta utgangspunkt i tidligere prosjekter eller eksempler basert på samme språk og rammeverk.

Gitpod @ NTNU

Gitpod har integrasjoner med skytjenester som gitlab og github, så en kan logge inn på og administrere kodelagre der. Gitpod kan også settes opp internt, og NTNU har sin egen gitpod-installasjon tilgjengelig på http://gitpod.stud.ntnu.no, som er integrert med IDI sin gitlab-tjeneste på http://gitlab.stud.idi.ntnu.no, så emner som bruker gitlab kan automatisk tilby gitpod som plattform for utvikling.

Alle kodelagre i IT1901 med eksempler og maler er ferdig gitpodifisert, se f.eks. javafx-templates. Dermed er de lette å bygge videre på, det er bare å trykke på Gitpod-knappen. Faktisk kan alle kodelagre på vår gitlab åpnes i gitpod for redigering av filer. Men for å kunne utvikle ordentlig, så må de altså gitpodifiseres vha. konfigurasjonsfilen .gitpod.yml og evt. en tilhørende docker-fil.

En alternativ måte å åpne gitpod på er å skrive inn nettadressen til gitpod-tjeneren (http://gitpod.stud.ntnu.no) etterfulgt av # og så adressen til gitlab-kodelageret som skal åpnes, f.eks. http://gitpod.stud.ntnu.no#https://gitlab.stud.idi.ntnu.no/it1901/javafx-templates.

Gitpod-arkitektur

VSCode i nettleseren fungerer nokså likt VSCode lokalt, den har tilgang til et filsystem med kjørebare programmer, og en del av filsystemet utgjør arbeidsområdet til VSCode. Forskjellen er at filsystemet og programmene er inni en virtuell maskin i skyen, og maskina startes opp kun for å kjøre VSCode i nettleseren. Ved oppstart klones kodelageret inn i maskina, og innholdet utgjør altså arbeidsområdet til VSCode. Konfigurasjonen av maskina og evt. oppstartsinstruksjoner leses ut av .gitpod.yml på toppnivå i det samme kodelageret.

gitpod arch

Den virtuelle maskina er flyktig, i den forstand at den forsvinner av seg selv hvis en lukker VSCode (eller fanen) eller lar være å bruke den en periode. Arbeidsområdet (og noen andre viktige filer) arkiveres imidlertid, slik at den kan startes opp igjen med i praksis samme innhold. Det blir som om en legger en bærbar i dvale og så vekker den til live igjen. Vanligvis vil en derfor fortsette med et eksisterende arbeidsområde fra hovedsiden til gitpod-tjenesten, i stedet for å starte en ny virtuell maskin med et nytt arbeidsområde fra (gitlab-nettsida for) kodelageret. Det er bare når en endrer oppsettet etter å ha redigert .gitpod.yml eller docker-fila at en må starte en helt ny maskin.

Hvis den virtuelle maskina er riktig satt opp (se under om konfigurering med .gitpod.yml), så kan en faktisk kjøre grafiske apper (f.eks. skrevet i JavaFX) og få dem vist inni gitpod eller i et separat nettleservindu. Dette krever at en "slår på" en virtuell grafisk skjerm som er en del av den virtuelle maskina. Se etter tallet 6080 i statuslinja nederst og trykk på det. Da spretter det opp et panel og en kan angi at 6080 (som representerer den virtuelle grafiske skjermen) skal vises i Preview-panelet eller i et eget nettleser-vindu.

Gitpod-bruk i emner

Gitpod kan brukes i emner på ulike måter. Emner som i dag distribuerer og deler innhold vha. git-kodelagre, kan gitpodifisere og få ekstra fordeler. Vi har identifisert en del scenarier for bruk av gitpod i emner, som en bør være fortrolig med.

Kode-eksempler

Fagstaben rigger et kode-eksempel i et prosjektoppsett som lar en kompilere og kjøre (generelt bygge). Prosjektet kan kreve verktøy og rammeverk som studentene ikke (ennå) har installert på egne maskiner. Studentene åpner kodelageret i gitpod og kan umiddelbart prøve det ut, endre på det og lære av det. Dersom en vil ta vare på eksemplet, må en opprette et eget kodelager og lagre (det endrede) innholdet der vha. git remote og git push.

Få veiledning

Studenten jobber med et prosjekt i et kodelager og trenger veiledning og sender lenke til en læringsassistent, som kan åpne gitpod og jobbe i samme oppsett som studenten. Hvis læringsassistenten har rettigheter til kodelageret (f.eks. fordi det ligger i en gitlab-gruppe satt opp av fagstaben), så kan hen legge tilbake endringer evt. lage et endringsforslag.

En annen mulighet er å ta et såkalt øyeblikksbilde (snapshot) som lagrer innholdet i arbeidsområdet og knytter det til en lenke. Læringsassistenten kan åpne denne lenka i gitpod og får samme innhold i sin gitpod som studenten hadde da øyeblikksbildet ble tatt. Fordelen er at en får delt innhold som det ikke naturlig å dele vha. selve kodelageret, f.eks. byggeresultater.

Øvingsopplegg

Fagstaben i et emne lager en gitlab-gruppe for emnet og semesteret, f.eks. tdt4100/v2022 og inni denne opprettes et gitpodifisert kodelager for hver student som både fagstaben og studenten har tilgang til. Hver uke publiseres nye øvingsoppgaver (tekster, startpakke med kode og evt. tester) som kopieres inn i hvert slikt kodelager, slik at studentene kan jobbe med dem og (implisitt) levere i sitt eget emne-kodelager.

Eksamen (!)

Eksamen kan rigges omtrent som et øvingsopplegg, med et personlig kodelager pr. student, med både oppgave og (etterhvert) besvarelse i.

Gitpod og bruk av git

Som nevnt klones kodelageret inn i den virtuelle maskina, og håndtering av kodelageret blir som ved kjøring på egen maskin, en bruker de vanlige git-kommandoene for å synkronisere med gitlab-tjeneren. En er ikke nødt til å lagre unna endringer i arbeidsområdet til tjeneren før den virtuelle maskina legges i dvale, hele arbeidsområdet arkiveres jo, men dersom en jobber med andre eller bruker VSCode i både gitpod og lokalt, så må en jo synkronisere filer via gitlab. Merk også at arkiverte arbeidsområder lagres ikke evig, de kastes etter noen uker uten bruk, så en må bruke git innimellom.

Å komme i gang er i grunnen mest komplisert, fordi utgangspunktet for kodelageret kan være så forskjellig.

Jobbe videre med andres kodelager

Hvis en skal jobbe videre med et kodelager som eies av andre, så må en lage en kopi (med eller uten kobling til originalen, med kobling så kalles det gjerne en "gaffel"). En oppretter da et nytt kodelager på gitlab f.eks. på egen bruker eller under en emne-gruppe. Navigér til egen bruker eller gruppa og velge New project under +-menyen øverst.

gitlab new project

Lag et tomt/blankt kodelager uten README, fordi hele innholdet skal overføres fra originalen. Hvert kodelager har sin egen adresse som brukes av git. Velg Clone-nedtrekksmenyen og kopier HTTPS-adressen (se under). Den skal brukes i en git-kommando for oppsett av git inni gitpod.

gitlab repo url

For å knytte kodelageret inni gitpod til det nye kodelageret på gitlab, skriv inn git remote set-url origin etterfulgt av kodelager-adressen fra forrige steg. Dermed vil alle kommandoer som opererer på git-tjeneren (en såkalt remote)gå mot det nye kodelageret. Kommandoen git push -u origin master brukes først for å overføre alt git-innhold m/historikk til det nye kodelageret sin master-grein, og siden kan en bare bruke git push for å oppdatere, evt. git pull for å hente ned endringer fra tjeneren.

Dersom en vil beholde knytningen til original-kodelageret, så endrer en først navnet på den opprinnelige kobling til f.eks. upstream med git remote rename origin upstream. Så opprettes det en ny kobling til det nye kodelageret med git remote add origin etterfulgt av kodelager-adressen fra forrige steg. Da vil vanlig bruk av git push og git pull gå mot det nye kodelageret, men en kan hente ned endringer fra original-kodelageret med git fetch upstream og innlemme dem med git merge upstream/master (og være beredt til å håndtere konflikter).

Opprette nytt kodelager på gitlab

Dette er i grunnen det enkleste. En bruker samme skjema som over, men velger å lage en README med det samme (om en glemmer det, så kan en lage en README på nettsiden til det nye kodelageret), og etter at kodelageret er opprettet, så åpner man bare gitpod på det nye kodelageret. Merk at selv om en kan åpne gitpod, så er ikke kodelageret automatisk gitpodifisert. Det gjør en ved å opprette .gitpod.yml og evt. en docker-fil med passende innhold, overføre disse tilbake med git push. For at det nye gitpod-oppsettet skal få effekt må en starte gitpod på nytt fra nettsida til kodelageret.

Overføre lokalt kodelager til gitlab (og gitpod)

Dersom du sitter med et kodelager lokalt på maskina di (som ikke finnes på gitlab), så kan det åpnes i gitpod ved å først overføre det til et kodelager på gitlab, for så å åpne det i gitpod. Prosessen blir litt som den første varianten over, en oppretter et kodelager på gitlab og angir så at det lokale kodelageret skal knyttes til det nye på gitlab (som en remote). Skriv inn git remote add origin etterfulgt av kodelager-adressen for det nye kodelageret (tilsvarende varianten over). Dette gjøres selvsagt i terminalen lokalt, evt. inni terminal-panelet i VSCode lokalt.

Til slutt åpnes det nye kodelageret på gitlab i gitpod.

Gitpod-konfigurasjon med .gitpod.yml og gitpod.Dockerfile (for den dristige)

Den virtuelle maskina konfigures vha. en spesiell fil med navn .gitpod.yml og en tilhørende docker-fil. Førstnevnte angir bl.a. hvilken docker-fil som beskriver alt som skal være ferdig installert i den virtuelle maskina. Det enkleste er å referere til en standard docker-fil laget for formålet f.eks. gitpod/workspace-full-vnc. Da får en en støtte for en rekke vanlige programmeringsspråk og kan kjøre grafiske apper på en slags virtuell skjerm som vises i gitpod eller et eget nettleser-vindu. Dersom en trenger noe ut over dette, kan en lage sin egen docker-fil som angir det som trengs ekstra på toppen av en standard en. Det er litt mye å dekke her, men under vises et enkelt oppsett for å bruke en nyere Java-versjon enn det som er standard i gitpod.

.gitpod.yml (styrer oppsettet):

image:
  file: .gitpod.Dockerfile

tasks:
  - init: sdk use java 16.0.1.hs-adpt

.gitpod.Dockerfile (angir innhold i virtuell maskin):

FROM gitpod/workspace-full-vnc

USER gitpod

RUN bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh \
             && sdk install java 16.0.1.hs-adpt \
             && sdk default java 16.0.1.hs-adpt"

Det viktige her er at

  • .gitpod.yml refererer til .gitpod.Dockerfile

  • .gitpod.Dockerfile bygger på (ved å referere til) gitpod/workspace-full-vnc

  • .gitpod.Dockerfile kjører en ekstra kommando(linje) som installerer java-distribusjonen 16.0.1.hs-adpt vha. sdk-programmet

  • .gitpod.yml kjører en kommando for å ta i bruk 16.0.1.hs-adpt i gitpod-terminal

En kan installere nær sagt hva som helst ved å skrive de riktige installasjonskommandoene inn i .gitpod.Dockerfile og ytterligere instruksjoner i .gitpod.yml. Det er litt fikkel, men en kommer langt ved å søke etter relevant docker-filer på Dockerhub og kopiere innhold derfra.

Kontinuerlig integrasjon

Smidig utfordring

Smidig utvikling baserer seg på å levere nyttig funksjonalitet ofte, slik at en 1) kan lære mer om hva som er riktig funksjonalitet og 2) får mest mulig verdi fra det som er utviklet. Dette krever at en itererer raskt og leverer nye versjoner ofte, uten at en samtidig ofrer kvalitet. En kan jo ikke sette nye funksjoner i produksjon flere ganger i uka eller om dagen, hvis en ikke er sikker på at det (fortsatt) virker som det skal.

Dette betyr at både det å sikre kvaliteten underveis og sette nye versjoner i produksjon må koste så lite som mulig. Kvalitetssikring handler mye om testing, men også om måter å sikre at koden har visse kvaliteter som gjør den grei å videreutvikle.

Testpyramiden

Testing (ved kjøring av kode) foregår gjerne på flere nivåer. Enhetstesting tester mindre kodeenheter som klasser og metoder isolert fra faktisk bruk i et stor program. Målet er ofte å sjekke korrekthet ved all mulig bruk. Integrasjonstester tester flere moduler og sub-systemer sammen, mer basert på hvordan de er ment å samhandle (bruke hverandre). En tester mer ut fra faktisk bruk, enn all mulig bruk, slik at mengden tester (mulighetsrommet) begrenses. Systemtester sjekker at totalsystemet virker som forventet slik det skal kjøre i virkeligheten. En tar gjerne utgangspunkt i bruksscenarier som er identifiser ved design av systemet, slik at bruken blir realistisk. Samtidig skal en jo også sjekke at systemet virker ved unormal og gal bruk, siden det også er realistisk. Noen tester er såkalte ende til ende-tester, hvor en simulerer konkret brukerinteraksjon gjennom et UI og ser at alt går rett for seg, f.eks. at databaser oppdateres som de skal og at responsen i UI-et er riktig.

En snakker gjerne om dette som en testpyramide, fordi mengden tester fra lavere til høyere nivå gjerne går ned fordi det blir uoverkommelig å teste alle mulighetene. Uansett ønsker en å vite at koden som helhet er godt gjennomtestet, altså har høy testdekningsgrad (test coverage). Uavhengig av antall tester er måten en rigger dem på forskjellig. Uten støtte fra testrammeverk og automatisering, vil kontinuerlig testing være ressurskrevende å gjennomføre i praksis.

Kodekvalitet

Ved siden av at koden virker, så ønsker en også at kvaliteter som sikrer at den er grei å jobbe med over tid, f.eks. at den er lett å få oversikt over, lese og forstå og modifisere for ulike formål. Dette er kvaliteter som kan sikres ved gjennomlesning, men det er også tidkrevende, og i det er mye å hente om en kan automatisere (deler av) det også, f.eks. sjekke av visse standarder for formatering og koding er fulgt.

Byggeverktøy og automatisering

Byggeverktøy hjelper en å automatisere mange av de aktivitetene som er nødvendige for å sjekke og sikre kvalitet, og som en uten automatisering ikke ville orket å gjennomføre. Mye er allerede automatisert av moderne IDE-er, f.eks. så kompileres gjerne koden kontinuerig så syntaktiske feil kan angis med røde krøllstreker og koden kan kjøres ved et knappetrykk i stedet for å skrive inn komplekse kommandlinjer i terminalen. Men ut over dette, så bruker en gjerne egne byggeverktøy, bl.a. fordi en ønsker et oppsett som er uavhengig av den enkelte IDE. IDE-en samspiller i stedet med byggeverktøyet, slik at en får det beste av begge verdener. Konfigurasjon gjøres med byggeverktøyet, men IDE-en har gjerne egne editorer for å forenkle jobben og utføre byggekommandoer.

Konfigurasjon av byggeverktøy

Konfigurasjon av byggeverktøy er komplekst fordi det er så mange variasjonsmuligheter når en skal kompilere, kjøre enhetstester og bygge hele systemer for alle mulige plattformer (språk og rammeverk). Tilnærmingen er likevel nokså lik, selv om detaljene varierer. F.eks. så bygger alle på informasjon om

  • hvordan kode og ressurser er strukturert i mapper

  • hvilke eksterne og interne moduler (kodebibliotek) en er avhengig av (og til hvilket formål)

  • hvilke byggeoppgaver som en ønsker utført (og i hvilke rekkefølge).

For å minimere mengden konfigurasjon så følger en gjerne visse konvensjoner, såkalt convention over configuration. For å gjøre det lettere å komme igang finnes ofte prosjektmaler og egne kommandoer for å instansiere disse, altså generere et startoppsett med visse elementer fylt inn spesifikt for anledningen.

Byggeverktøy er avhengig av høy grad av fleksibilitet og utvidbarhet, siden feltet er i rivende utvikling og en er nødt til støtte integrasjon av nye rammeverk og verktøy. Dette gjøres gjerne vha. såkalte tillegg (plug-ins), og mange av standardfunksjonene som kompilering og pakking av filer er ofte realisert som tillegg. Disse krever ytterligere konfigurasjon ut over den generelle informasjonen nevnt over.

Maven

Det mest modne og utbredte byggeverktøyet for Java-plattformen er Apache maven, med Googles gradle som en konkurrent i sterk vekst (se https://www.jetbrains.com/lp/devecosystem-2020/java/). Disse har litt ulik terminologi og logikk, men forskjellene er mindre enn en skulle tro ut fra hvor sterke preferanser mange har. Den tydeligste forskjellen er knyttet til at konfigurasjon av gradle tillater bruke av et fullt programmeringsspråk (groovy og i senere tid kotlin), med de fordeler og ulemper det medfører. Her tar vi uansett utgangspunkt i maven, selv om det samme kan gjøres (og nokså likt) med gradle.

Standard mappestruktur for kode og ressurser

Med mindre noe annet er konfigurert, krever maven sine standardinnstillinger fire mapper for java (hvis mappen trengs, da):

javafx template structure
  • src/main/java inneholder "produktiv" java-kode

  • src/main/resources inneholder ressurser, f.eks. fxml- og png-filer

  • src/test/java inneholder testkode

  • src/test/resources inneholder ressurser spesifikke for testene, f.eks. datafiler

I utklippet til høyre ser vi at mappene src/main/java, src/main/resources og src/test/java er i bruk, og hver av disse har en app-mappe som tilsvarer java-pakken app.

Maven-konfigurasjon med pom.xml

Fila pom.xml brukes for å konfigurere bygging med maven og en mappe med en slik fil forteller at mappa inneholder et prosjekt satt opp for å bli bygget med maven. Dersom man står i en slik mappe så kan mvn-kommandoen bruke for å utføre ulike byggeoppgaver.

Pom-fila (kortform for pom.xml-fila) inneholder ulike xml-blokker med diverse informasjon som styrer byggeoppgavene, f.eks:

  • nødvendige biblioteker for kompilering, kjøring og testing

  • versjon og konfigurasjon av maven-tillegg som trengs i bygget

  • konfigurasjon av kompilering (java-versjon) og kjøring (hovedklasse)

Eksemplene under finner du i javafx-template-prosjektet på gitlab. I første omgang tar vi utgangspunkt i én-modul-prosjektet javafx-template.

Avhengigheter

Såkalte avhengigheter, f.eks. kode-biblioteker som vårt prosjekt trenger, er angitt i <dependencies>-blokka med <dependency>-elementer, f.eks. så sier xml-snutten under at koden vår trenger modulen med artifactId lik javafx-controls, groupId lik org.openjfx og version lik 16 for å kompilere og kjøre. groupId, artifactId og version er såkalt GAV-koordinater som unikt/globalt identifiserer moduler i maven-økosystemet. Med GAV-koordinatene kan maven spørre et maven-kodelager (repository) lokalt eller på nettet (f.eks. Maven central) om den tilsvarende jar-fila med den faktiske koden for modulen. Denne kan maven så automatisk laste ned så den kan brukes ved kompilering og kjøring.

        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>16</version>
        </dependency>

En kan angi hva slags sammenheng en trenger en avhengighet i med <scope>-elementet, f.eks. trenger vi jupiter-api-et til testing:

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.7.2</version>
            <scope>test</scope>
        </dependency>

<scope>-elementet kan ha ulike verdier for å angi spesifikk bruk av avhengigheter:

  • compile (standardverdien) - kompilering (og kjøring) av vanlig kode

  • test - kompilering (og kjøring) av testkode

  • runtime - trengs ved kjøring, men ikke kompilering

  • provided - trengs ved kompilering, men ikke ved kjøring (er implisitt med)

Avhengigheten til jupiter krever flere moduler, og siden alle skal ha samme versjon, så kunne vi definert det som en konstant i <properties>-blokka og så referert til denne i stedet:

    <properties>
        <jupiterVersion>5.7.2</jupiterVersion>
    </properties>
    ...
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${jupiterVersion}</version>
            <scope>test</scope>
        </dependency>

Her deklareres konstanten jupiterVersion med verdien 5.7.2 og i <version>-elementet vil referansen ${jupiterVersion} bli byttet ut med denne verdien. Slike konstanter og referanser kan brukes mange steder for å gjøre pom-koden ryddigere og enklere å endre.

Maven-tillegg

Maven-tillegg aktiveres og konfigureres i <plugins>-blokka med <plugin>-elementer:

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <release>16</release>
                </configuration>
            </plugin>

Her aktiveres og konfigureres kompilatoren slik at den bruke Java 16 til kompilering og kjøring (den er automatisk aktivert for java-prosjekter, men vi må konfigurere den for en spesifikk java-versjon). Dette plukkes opp av IDE-en, slik at editoren støtter full Java 16-syntaks, inkluder tekstblokker (java 15) og verdiobjekter (records).

Maven-tillegg identifiseres med samme slags GAV-koordinater som moduler generelt, og en kan tenke på slike tillegg som spesielle moduler som implementerer nye typer byggeoppgaver. Tillegg-modulene lastes ned automatisk når kjøring av mvn-kommandoen (se under) krever det. Hvilke koordinater og konfigurasjonselementer en må bruke, må en nesten finne fra eksempler og søk på nettet. javafx-template-prosjektet konfigurerer tre tillegg, maven-compiler-plugin for kompilering, maven-surefire-plugin for kjøring av tester og javafx-maven-plugin for kjøring av javafx-apper. Sistnevnte konfigereres bl.a. med hvilken klasse som er prosjektets app-klasse i mainClass-elementet.

mvn-kommandoen

Med tilleggene over aktivert, så kan en bruke mvn-kommandoen for å utføre byggeoppgavene de implementerer:

  • mvn compile kompilerer koden (som er endret siden sist)

  • mvn test kjører alle testene (klassen som identifiseres som testklasser)

  • mvn javafx:run kjører javafx-appen

Argumentet til mvn er enten en spesifikk byggeoppgave eller en såkalt fase, som er maven sitt hovedkonsept for å organisere og sekvensiere byggeoppgaver. Hvert type prosjekt/modul, f.eks. java-prosjekt, har et sett faser med tilhørende oppgaver. F.eks. så utføres byggeoppgaven compile:compile (compile-oppgaven til kompilator-tillegget) som en del av fasen compile. Når en skriver mvn compile så betyr det altså implisitt å kjøre kompilatoren. Faser har en implisitt innbyrdes rekkefølge, så hvis en kjører én fase, så kjøres automatisk fasene tidligere i rekken. F.eks. så kjøres fasene compile og test-compile automatisk før test-fasen når en kjører mvn test. Se maven sin side om dette for detaljene.

Merk at en kan oppgi flere (uavhengige) byggeoppgaver og/eller faser etter hverandre, f.eks. så kjører en ofte tester før en kjører programmet med mvn test javafx:run.

<configuration>-elementene inni <plugin>-deklarasjonen av tilleggene angir diverse verdier som tilleggene bruker når deres byggeoppgaver kjøres, om disse kjøres direkte eller som en del av en fase. Noen ganger er det nyttig å kjøre byggeoppgaver med spesifikke alternative verdier, f.eks. kan en ønske å kjøre én bestemt test(klasse) og ikke alle sammen på en gang, eller kjøre en alternativ app-klasse i stedet for den som er angitt i pom-en. Dette lar seg ofte gjøre ved å angi "-Dkonstant=verdi" som kommandolinjeargument, hvor konstant og verdi er spesifikk for tillegget og anledningen. (Anførselstegnet brukes for å unngå at spesielle tegn i konstantnavnet og verdien tolkes som noe annen en vanlige tegn.) F.eks. kjører man testklassenn pack.TestClass med mvn test "-Dtest=pack.TestClass" og app-klassen pack.AppClass med mvn javafx:run "-DmainClass=pack.AppClass". Som regel er konstantnavnet det samme som det tilsvarende konfigurasjonselementen.

Resultater fra byggingen

Hensikten med hver byggeoppgave er ofte å generere filer, ofte kalt artifakter, som er avledet fra filer i prosjektet eller byggeoppgaver kjørt tidligere. F.eks. så er er resulatet fra kompilering class-filer og kjøring av tester gir testrapporter. De fleste slike byggeresultater (litt avhengig av konfigureringen) havner i target-mappa, f.eks. havner class-filene i target/classes-mappa (sammen med ressurser fra process-resources-fasen). Disse klassene (og ressursene) plukkes opp av jar:jar-oppgaven i package-fasen og pakkes i en jar-fil som også havner i target. Til slutt kan deploy-fasen brukes til å laste jar-filen opp på en server, så den kan lastes ned av andre og evt. settes i produksjon.

En nyttig fase som ofte inkluderes først (eller alene) i en mvn-kommando er clean. Den fjerner hele target-mappa, slik at byggingen starter med "blanke ark". Dette kan gjøre det enklere å finne feil i oppsettet, fordi en da inngår å kjøre byggeoppgaver på "gamle" byggeresultater.

Modularisering

En ønsker ofte å dele opp et prosjekt i flere moduler, slik at hver del er relativt isolert fra og uavhengig av hverandre. Dette gjør delene mer gjenbrukbare og kan gjøre det ryddigere og enklere å koordinere gruppen av utviklere som utviklere prosjektet som helhet. F.eks. kan en ha separate moduler for kjernekoden, brukergrensesnittet og web-tjenesten. Med klare skiller mellom modulene, så sauses de ikke så lett sammen og utviklerne går mindre i beina på hverandre.

Det kan fortsatt være avhengigheter mellom modulene, men disse er eksplisitte, på samme måte som moduler har avhengigheter til biblioteksmoduler skrevet av andre og gjort tilgjengelig på sentrale tjenere. Internt i en virksomhet kan modulene i et prosjekt fungere som bibliotelsmoduler i et annet. Modularisering er et viktig virkemiddel for å begrense kompleksiteten til store systemer.

Flér-modul-prosjekter i maven

Maven støtter oppdeling av et stort prosjekt i flere moduler. Hver av modulene blir på en måte selvstendige prosjekter under et felles hovedprosjekt. En flérmodul-variant av javafx-template-prosjektet finner du i modules-template-mappa. Denne mappa utgjør hovedmodulen, med moduler for kjernekode og ui i hver sine undermapper, henholdsvis core og ui. Merk at en kan ha flere nivåer enn dette, altså undermoduler med moduler under der igjen, men her forholder vi oss til bare to nivåer. For et større eksempel, se todo-list.

En hovedmodul angir undermodulene i pom-fila si, i <module>-elementer i <modules>-blokka:

    <modules>
        <module>core</module>
        <module>ui</module>
    </modules>

Undermodulene referere tilbake til hovedmodulen ved å angi GAV-koordinatene i <parent>-elementet:

    <parent>
        <groupId>it1901</groupId>
        <artifactId>modules-template</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath>..</relativePath>
    </parent>

Undermodulene deklarerer egne artifactId-verdier, mens groupId og version arves fra hovedmodulen. Dermed får hoved- og undermoduler egne GAV-koordinater hvor groupId- og version-elementene er felles. Disse kan brukes for å angi avhengigheter mellom undermodulene, f.eks. fra ui-modulen til kjernemodulen.

Hovedprosjektet blir mer nyttig ved at pom-fila kan inneholde konstanter, avhengigheter og konfigurasjon av tillegg som er felles for undermodulene og gjøre det lettere å sikre at de bruker de samme konstantverdiene, avhengighetene og tilleggene.

Felles avhengigheter deklareres inni <dependencyManagement>-blokka og tillegg inni <pluginManagement>-blokka. I undermoduler angir man dem som vanlig, men kan utelate detaljer der deklarasjonene i hovedmodulen duger. I ui-modulen aktiveres f.eks. maven-compiler-plugin- og maven-surefire-plugin-tilleggene med versjonene og konfigurasjonene angitt i hovedmodulen, og i tillegg deklareres javafx-maven-plugin-tillegget, som ikke er nevnt i hovedmodulen:

        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
        		<artifactId>maven-surefire-plugin</artifactId>
	        </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.6</version>
                <configuration>
                    <mainClass>app.App</mainClass>
                </configuration>
            </plugin>
        </plugins>
mvn-kommandoer i flér-modul-prosjekter

mvn-kommandoer med faser som argument, f.eks. clean, compile og test, kan utføres i mappa for hovedprosjektet. Fasene blir da utført i underprosjektene i en rekkefølge utledet fra innbyrdes avhengigheter. Hvis f.eks. ui-modulen avhenger av core-modulen, så vil kommandoen mvn compile utført i hovedmodulen gjøre at compile-fasen først utføres i core-modulen og deretter i ui-modulen, siden det ligger i kortene at sistnevnte er avhengig av byggeresultatene fra førstnevnte.

Det er litt verre med kjøring av spesifikke byggeoppgaver, f.eks. javafx:run. Det går bare hvis tilsvarende maven-tillegg er aktivert for hovedmodulen og alle undermodulene. Ofte må man angi -pl-opsjonen etterfulgt av undermodulen for å begrense kjøringen til den relevante modulen. F.eks. kan man kjøre mvn -pl ui javafx:run i mappa for hovedmodulen i stedet for kjøre cd ui etterfulgt av mvn javafx:run.

IDE-støtte for maven

De fleste IDE-er gjenkjenner mapper med pom.xml-fil som prosjekter konfigurert med maven og kan rette seg litt etter konfigurasjonen, f.eks. for å sikre at (implisitt) kompilering og smarte editorfunksjoner bruker riktig Java-versjon og gi tilgang til pakker og klasser i biblioteker som er deklarert som avhengigheter.

Noen IDE-er går enda lenger og tilbyr editorer for å redigere pom-filer og paneler og dialoger for å utføre utføre byggeoppgaver tilsvarende mvn-kommandoer.

Bygging i gitlab

Gitlab har støtte for å kjøre de samme byggeoppgavene som en kan kjøre lokalt. Ut over å være en ekstra kvalitetssikring, så hjelper det å gjøre oppsettet av prosjektet uavhengig av oppsettet på den enkelt utvikler sin maskin. Det er lett å komme i skade for å gjøre kjøring av byggeoppgaver avhenger av lokalt oppsett, så dette sikrer et reproduserbart bygg.

En fil ved navn .gitlab-ci.yml brukes for å angi det felles byggeoppsettet, og når ulike byggeoppgaver skal utføres. F.eks. kan en be om at alle testene kjøres hver gang ny kode innlemmes i hovedgreina til prosjektet.

4. Forelesning

Lysarkene gir en oversikt over SCRUM og Gitlab elementer som kan brukes i prosjektet.

5. Forelesning

Lysarkene Q&A.

6. Forelesning

Lysarkene Pair programming + more on Git.

7. Forelesning

Oppsummering av øving 1

Lysarkene oppsummerer den individuelle, obligatoriske øving 1

Kodestank

Lysark om såkalt "kodestank" (code smell)

8. Forelesning

Lysarkene Unit testing.

9. Forelesning

Lysarkene Documentation.

10. Forelesning

Lysarkene 2nd Group assignment + Modularization. Lysarkene Code quality.

2020

Programvareutvikling

Utvikling og kildekodehåndtering

Git-demonstrasjon

Byggeverktøy og (litt om) testing

Gitlab

Eksempel på arbeidsfly

Dokumentasjon

Enhetstesting

Kodekvalitet

Moduler og modularitet

Designprinsipper og UML

Git og gitlab

REST API

Generelle/felles problemer

Smidige verktøy

Kontinuerlig integrasjon

Kildekodehåndtering