Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • akselsa/course-material
  • it1901/course-material
2 results
Show changes
Showing
with 493 additions and 154 deletions
howto/images/mac_02_check_false.png

26.9 KiB

howto/images/mac_03_extract.png

57.5 KiB

howto/images/mac_04_move.png

28.7 KiB

howto/images/mac_05_env01.png

24.1 KiB

howto/images/mac_06_env02.png

25 KiB

howto/images/mac_07_apply_env.png

25.8 KiB

howto/images/mac_08_env03.png

38.1 KiB

howto/images/mac_09_check_true.png

32.9 KiB

howto/logos/java.png

6.52 KiB

......@@ -2,4 +2,4 @@
.gradle
# Ignore Gradle build output directory
build
build/
# Materiale laget med asciidoctor
Mappa **asciidoctor** inneholder tekstlig materiale, mens **revealjs**-mappa
inneholder lysarkene.
## Hvordan bygge
Bygging av både dokumentasjon og lysark skjer med gradle.
Bruk `gradle asciidoctor` for å generere HTML for det tekstlige og `gradle asciidoctorRevealJs` for lysarkene.
Konvertering kan også gjøres med den generelle `gradle build`.
== Multi-eksempel
Dette eksemplet viser hvordan rigger et multi-prosjekt med gradle,
med del-prosjekter for
- domenelogikk (core)
- brukergrensesnitt med JavaFX og FXML (fx)
- React-basert web-klient (react)
- web-server med JAX-RS og Jersey (jersey)
=== Bygging med Gradle
https://docs.gradle.org/current/userguide/userguide.html[Gradle] er et såkalt byggesystem, som automatiserer oppgaver knyttet til utvikling og kvalitetssikring av programvare, f.eks.
kompilering, kjørings av enhetstester og integrasjonstester, konvertering av dokumentasjon til HTML, pakking som kjørbart program, osv.
Det finnes teknisk sett ikke grenser for hva et byggesystem kan gjøre, generelt handler det om å automatisere bort alt praktisk fikkel,
som ellers hindrer en å jobbe effektivt.
Det finnes flere byggesystem, de viktigste i Java-verdenen er https://maven.apache.org[Maven] og Gradle, mens Javascript-verdenen har sine, bl.a. https://docs.npmjs.com[npm].
I dette prosjektet har vi valgt å bruke Gradle, fordi det er enklere å skrive og lese oppsettet og trigge kjøring, mer fleksiblet og
det vi ønsker å automatisere i akkurat dette prosjektet er innafor det Gradle håndterer godt. For andre Java-prosjekter https://phauer.com/2018/moving-back-from-gradle-to-maven/[kan Maven være et bedre alternativ].
Siden ett av prosjektet bruker Javascript og npm, så
bruker vi et eget Gradle-tillegg som bygger bro til npm.
Gradle er bygget rundt tre sentrale elementer:
- oppgaver (tasks) - det som automatiseres, f.eks. kompilering, kjøring av tester, osv.
- prosjekter - enhetene som oppgaver utføres innenfor
- tillegg (plugins) - angir hvilke oppgaver som hører til et visst type prosjekt.
Oppsettet for bygging består bl.a. i å angi hvilke (del)-prosjekter som inngår i hovedprosjektet,
hvilke avhengigheter det er til eksterne moduler og mellom prosjektene, hvilke tillegg som bør aktiveres for disse prosjektene
(basert på hva slags type prosjekt det er) og hvordan hver type oppgave skal konfigureres.
NOTE: Det litt spesielle med Gradle er at filene for å sette opp et Gradle-bygg egentlig er https://docs.gradle.org/current/userguide/groovy_build_script_primer.html[kode skrevet i det Java-lignende språket Groovy].
Koden brukes primært til konfigurasjon, altså bygge opp beskrivelsen av oppgavene, prosjektene og tilleggene og. API-et er utformet slik at koden skal se deklarativ ut.
En kan flette inn vanlig kode også, hvis en skjønner litt om hvordan API-et virker. Dette gir enorm fleksibilitet, men kan også gjøre filene vanskelige å forstå.
De to viktigste filene for å sette opp et Gradle-bygg er *settings.gradle* og *build.gradle*.
= Kontinuerlig integrasjon
link:slides/ci.html[Lysark]
== 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 https://martinfowler.com/articles/practical-test-pyramid.html[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 https://en.wikipedia.org/wiki/Convention_over_configuration[_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 https://maven.apache.org[Apache maven], med Googles https://gradle.org[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):
image::images/javafx-template-structure.png[width=250, float="right"]
** *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 https://gitlab.stud.idi.ntnu.no/it1901/javafx-template/-/tree/main/javafx-template[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. https://mvnrepository.com/repos/central[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.
[,xml]
----
<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:
[,xml]
----
<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:
[,xml]
----
<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:
[,xml]
----
<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 https://www.baeldung.com/java-text-blocks[_tekstblokker_] (java 15) og https://docs.oracle.com/en/java/javase/16/language/records.html[_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 http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html[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 https://gitlab.stud.idi.ntnu.no/it1901/javafx-template/-/tree/main/modules-template[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 https://gitlab.stud.idi.ntnu.no/it1901/todo-list[todo-list].
En hovedmodul angir undermodulene i pom-fila si, i `<module>`-elementer i `<modules>`-blokka:
[,xml]
----
<modules>
<module>core</module>
<module>ui</module>
</modules>
----
Undermodulene referere tilbake til hovedmodulen ved å angi GAV-koordinatene i `<parent>`-elementet:
[,xml]
----
<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:
[,xml]
----
<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.
= Gitpod
link:slides/gitpod.html[Lysark]
== 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. https://gitlab.stud.idi.ntnu.no/it1901/javafx-templates[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.
image::images/gitpod-arch.png[width=800]
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.
image::images/gitlab-new-project.png[width=800]
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.
image::images/gitlab-repo-url.png[width=300]
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å https://hub.docker.com/search?q=&type=image[Dockerhub] og kopiere innhold derfra.
lectures/asciidoc/images/gitlab-new-project.png

216 KiB

lectures/asciidoc/images/gitlab-repo-url.png

13 KiB

lectures/asciidoc/images/gitpod-arch.png

896 KiB

lectures/asciidoc/images/javafx-template-structure.png

68 KiB

:sourcedir: .
:toclevels: 3
= Forelesninger
== 2021
=== Introduksjon til emnet
link:slides/01-course-intro.html[Lysarkene] gir en oversikt over organiseringen av emnet
link:slides/02-git-plus-plus.html[Lysarkene] gir en oversikt over git, gitlab og gitpod
include::{sourcedir}/gitpod.adoc[leveloffset=+2]
include::{sourcedir}/ci.adoc[leveloffset=+2]
=== 4. Forelesning
link:slides/04-lecture.html[Lysarkene] gir en oversikt over SCRUM og Gitlab elementer som kan brukes i prosjektet.
=== 5. Forelesning
link:slides/05-lecture.html[Lysarkene] Q&A.
=== 6. Forelesning
link:slides/06-lecture.html[Lysarkene] Pair programming + more on Git.
=== 7. Forelesning
==== Oppsummering av øving 1
link:slides/individuell-oblig.html[Lysarkene] oppsummerer den individuelle, obligatoriske øving 1
==== Kodestank
link:slides/kodestank.html[Lysark] om såkalt "kodestank" (code smell)
== 8. Forelesning
link:slides/08-lecture.html[Lysarkene] Unit testing.
== 9. Forelesning
link:slides/09-lecture.html[Lysarkene] Documentation.
== 10. Forelesning
link:slides/12-modular.html[Lysarkene] 2nd Group assignment + Modularization.
link:slides/10-lecture-code-quality.html[Lysarkene] Code quality.
== 2020
=== Programvareutvikling
link:slides/02-software-development.html[Lysarkene]
=== Utvikling og kildekodehåndtering
link:slides/03-dev-and-scm.html[Lysarkene]
=== Git-demonstrasjon
link:slides/04-git-demo.html[Lysarkene]
=== Byggeverktøy og (litt om) testing
link:slides/06-build-tools-and-some-testing.html[Lysarkene]
=== Gitlab
link:slides/07-gitlab.html[Lysarkene]
=== Eksempel på arbeidsfly
link:slides/08-workflow-example.html[Lysarkene]
=== Dokumentasjon
link:slides/09-documentation.html[Lysarkene]
=== Enhetstesting
link:slides/10-unit-testing.html[Lysarkene]
=== Kodekvalitet
link:slides/11-code-quality.html[Lysarkene]
=== Moduler og modularitet
link:slides/12-modular.html[Lysarkene]
=== Designprinsipper og UML
link:slides/13-solid-uml.html[Lysarkene]
=== Git og gitlab
link:slides/14-git-gitlab.html[Lysarkene]
=== REST API
link:slides/17-rest-api.html[Lysarkene]
=== Generelle/felles problemer
link:slides/19-common-issues.html[Lysarkene]
=== Smidige verktøy
link:slides/agiletools.html[Lysarkene]
=== Kontinuerlig integrasjon
link:slides/ci.html[Lysarkene]
=== Kildekodehåndtering
link:slides/scm.html[Lysarkene]
plugins {
id 'org.asciidoctor.jvm.convert' version '3.0.0-alpha.3'
id 'org.asciidoctor.jvm.revealjs' version '3.0.0-alpha.3'
}
repositories {
jcenter()
maven {
url "https://plugins.gradle.org/m2/"
}
maven {
url 'http://rubygems-proxy.torquebox.org/releases'
}
}
dependencies {
asciidoctorGems 'rubygems:asciidoctor-revealjs:2.0.0'
}
asciidoctor {
sourceDir 'asciidoc'
sources {
include '*.adoc'
}
outputDir file('build/docs/asciidoc')
resources {
from('asciidoc') {
include '**/*.png'
}
into '.'
}
logDocuments = true
}
asciidoctorj {
modules {
// diagram.use()
diagram.version '1.5.16'
}
// useIntermediateWorkDir = true
attributes toc: 'left',
'source-highlighter': 'highlight.js'
/*
extensions {
block_macro (name: 'tweet') { parent, target, attributes ->
String content = """<div class="tweet" data-src="https://twitter.com/${target}/status/${attributes.get('1')}"></div>"""
config.remove 'content_model'
createBlock(parent, "pass", [content], [:], config)
}
}
*/
}
asciidoctorRevealJs {
sourceDir 'revealjs'
sources {
include '*.adoc'
}
outputDir file('build/docs/revealjs')
resources {
from('revealjs') {
include 'images/*'
include '**/*.css'
}
into '.'
}
attributes 'sourceDir': 'revealjs',
'imagesDir': 'revealjs',
'icons':'font',
'iconfont-name': 'fontawesome-4.5.0'
revealjsOptions {
controls = true
slideNumber = true
progressBar = true
pushToHistory = true
overviewMode = true
touchMode = true
backgroundTransition = 'convex' //none , fade, slide, convex, concave, zoom
theme = 'white' //'black', 'beige' , 'league', 'night', 'serif', 'simple', 'sky', 'solarized'
}
plugins 'rajgoel/chart/Chart.min.js'
//plugins 'IainAndrew/footer-reveal/footer-reveal.min.js'
}
revealjs {
version '2.0.0' // why not '3.8.0'
templateGitHub {
organisation = 'hakimel'
repository = 'reveal.js'
tag = '3.8.0'
}
}
revealjsPlugins {
github 'rajgoel', {
organisation = 'rajgoel'
repository = 'reveal.js-plugins'
branch = 'master'
}
/*github 'IainAndrew', {
organisation = 'IainAndrew'
repository = 'footer-reveal'
branch = 'master'
}*/
}
build.dependsOn 'asciidoctorRevealJs'