# Development Workflows - Notes
## REPL - read eval print loop
- Interactive programming environment
- Type `]` to enter package mode, `backspace` to exit
- Tab completion
- `?` for help mode
- `;` for shell mode
## Environments
- Each project has its own environment (plus the global one)
- Isolated set of packages
- Different projects can use different versions of the same package
- `Project.toml` and `Manifest.toml` files
- Project.toml: what you want (direct dependencies, compatibility bounds)
- Manifest.toml: what you have (all transitive dependencies, exact versions)
- -> reproducibility
- If you switch julia versions a lot, use versioned manifests like `Manifest-v1.11.toml` to avoid recompiling everything.
- In an environment, only add the packages you directly use
- Development tools should be in the global environment
In the following two modes:
1. Using a list of packages
2. Working on a package (this can be OSCAR or your toy package that depends on OSCAR)
Goals:
- get your own code running
- edit code and see changes immediately (without restarting julia)
## Using packages
- have a folder for the environment
- start julia with `julia --project=/path/to/folder` or `julia --project` for current folder
- `] add PackageName` adds a package
- `] add PackageName@version` for a specific version
- `] add PackageName#rev` for a git revision (branch, tag, commit)
- `] add https://github.com/user/repo#rev` for a package not registered in the registry (e.g. your own fork of OSCAR)
- `] up` updates all packages in the environment
- `] resolve` tries to resolve the environment again (useful if you manually edited Project.toml), i.e. re-creates Manifest.toml
- `] rm PackageName` removes a package
- `] instantiate` downloads and installs all packages in the Manifest.toml (for reproducing an environment)
- `] status` shows the packages in the environment, with `-m` also the transitive dependencies
- `using PackageName` to load a package
- `include("file.jl")` to load your own code
- `Revise.includet("file.jl")` (instead of the previous one) to load your own code with hotloading
- until julia 1.11: only reloads methods, no structs or constants
- from julia 1.12: also reloads structs and constants (experimental)
Up/downsides:
- good for small scripts
- only works if you don't need to change the code of the packages you use
- no tests, no CI, no docs
- order of file inclusion may matter, and needs to be memorized by you
## Developing packages
- Fork and clone the package repo (e.g. OSCAR), `gh repo fork oscar-system/Oscar.jl`
- (or create a new empty folder with the following structure)
- Folder structure of a package:
- `Project.toml` and `Manifest.toml`
- `src/` folder with the code (main file named like the package, e.g. `Oscar.jl`, that includes all other files)
- `test/` folder with tests (usually a file `runtests.jl` that includes other test files)
- optional: `docs/` folder with documentation
- "package folder" = "environment folder"
- move into the package folder, start julia with `julia --project`
- the pkg mode commands we learned previously work here as well, but they now affect just the dependencies of this package
- you usually only need `] resolve` and `] up` after pulling changes from git that touch the `Project.toml`
- `using PackageName` to load the local version of the package
- if you **first** do `using Revise` and **then** `using PackageName`, you get hot reloading of your package code
- until julia 1.11: only reloads methods, no structs or constants
- from julia 1.12: also reloads structs and constants (experimental)
Remarks to importing and exporting:
- to make a function from a package available outside (e.g. in the REPL) without full qualification (i.e. `PackageName.functionname`), you need to export it in the package code: `export functionname` in the package code
- `using PackageName` makes all exported names available
- to add new dispatches to an existing function from a dependency, you need to `import DependencyPackageName: functionname` first, then you can add new methods with `function functionname(args...) ... end`
## Break for exercises 1 and 2
## Tests
- tests are important...
- to verify that your code works as intended
- to verify that future changes do not break existing functionality
- more details:
- usually in the `test/` folder of a package, and then separated into multiple files/folders similar to the `src/` folder
- Running tests:
- in the REPL, `] test` runs (all) the tests of the current package
- in OSCAR we have `Oscar.test_module("foldername"; new=false)` and `Oscar.test_experimental_module("experimentalfoldername"; new=false)` to run just the tests of one folder inside `test/`, with `new=true` it will test in a new temporary session
- Testing framework: `Test` (part of the standard library)
- `@test expr` where `expr` evaluates to a boolean checks if `expr` is true
- `@test_throws ExceptionType expr` checks if evaluating `expr` throws an exception of type `ExceptionType`
- `@testset "name" begin ... end` groups multiple tests together
- In OSCAR, all tests are run on GitHub Actions for each pull request (continuous integration, CI)
## Documentation
- Documentation is important...
- to make your code usable by others (and yourself in the future)
- to explain design decisions
- more details:
- Documentation consists of two parts:
1. docstrings in the code
2. markdown pages with more extensive documentation, that include the docstrings
- Docstrings:
- attached to methods, types, modules, constants, ...
- written in markdown
- placed immediately before the item they document
- the first line is indented by exactly 4 spaces, and usuall contains the method signature
- accessed with `?` in the REPL, e.g. `?functionname`
- can be multi-line, and can use markdown formatting
- can contain code examples, see *doctests* below
- Documentation pages:
- in the `docs/src/` folder of a package
- written in markdown
- can include docstrings with `@docs`
- build documentation locally:
- in general with `julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate(); include("docs/make.jl")'` in the package folder
- for OSCAR with `Oscar.build_doc()` in the REPL
## Docstrings
- examples in docstrings that are also executed as part of the tests
- output is checked one to one, so it must match exactly (including types, formatting, ...)
- no randomness, no system-dependent output (e.g. memory addresses)!
- but there are ways to deal with this, see
- implemented as a markdown code block starting with `jldoctest` and then a REPL-like example
```jldoctest
julia> f(x) = x^2;
julia> f(3)
9
```
- CI will run all doctests for you automatically and report errors (on every pull request in OSCAR)
- locally, you can run all doctests with `using Documenter; Documenter.doctest(Oscar)` in the REPL
- to automatically repair all doctests use `using Documenter; Documenter.doctest(Oscar; fix=true)` in the REPL
- this will change your files!
- check the changes, and only commit them if they are correct
- to only repair few specific docstrings (in OSCAR), use `Oscar.doctest_fix("path/to/file.jl")` instead