You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Centre for Crop Systems Analysis - Wageningen University
3
+
Ana Ernst & Alejandro Morales
4
+
Centre for Crop Systems Analysis - Wageningen University
5
5
6
6
This guide helps VPL users interested in (1) managing packages within the VPL-verse, (2) managing reproducible environments, and, (3) developing models into VPL-modules, customization, and extending VPL's functionality under their authorship. It equips them with essential Julia tools and functionalities to take control of these aspects, enhancing the VPL experience.
7
7
@@ -11,15 +11,15 @@ The guide introduces the user to the Julia programming language's package manage
11
11
12
12
Unlike traditional package managers, that install and manage a single global set of packages, [Pkg.jl](https://pkgdocs.julialang.org/v1/) does things differently, it uses **environments** - independent sets of packages that can be local to an individual project, and can be shared and selected by name.
13
13
14
-
In each of these environments, there is a neat list of the packages associated with the project and their exact versions, kept in a special file called `Manifest.toml`. You can check this file in the project repository and keep track of the versions of the different packages that you use on your project, significantly improving the reproducibility of projects.
14
+
In each of these environments, there is a neat list of the packages associated with the project and their exact versions, kept in a special file called `Manifest.toml`. You can check this file in the project repository and keep track of the versions of the different packages that you use on your project, significantly improving the reproducibility of projects.
15
15
16
-
So, if you ever want to work on a project from a different computer or share with with someone else, you can just bring back the environment using its manifest file. That way, you're all set with the right packages without any fuss.
16
+
So, if you ever want to work on a project from a different computer or share with with someone else, you can just bring back the environment using its manifest file. That way, you're all set with the right packages without any fuss.
17
17
18
18
Julia environments are *stackable* - you can overlay one environment with another and have access to additional packages outside of the original environment.
19
19
20
-
This makes it easy to work on a project, which provides the primary environment, while still having access from the REPL to all your usual dev tools like profilers, debuggers, and so on, just by having an environment including these dev tools later in the load path.
20
+
This makes it easy to work on a project, which provides the primary environment, while still having access from the REPL to all your usual dev tools like profilers, debuggers, and so on, just by having an environment including these dev tools later in the load path.
21
21
22
-
So, it's like having your main work area and then, when needed, you can simply add more tools on top of it without disturbing your primary setup. This keeps things organized and makes it easy to switch between different tasks in Julia.
22
+
So, it's like having your main work area and then, when needed, you can simply add more tools on top of it without disturbing your primary setup. This keeps things organized and makes it easy to switch between different tasks in Julia.
23
23
24
24
## Getting started with Pkg.jl and environments
25
25
@@ -37,46 +37,46 @@ To better understand how Julia works with environments, we have two different ty
37
37
38
38
***Project Environment**
39
39
40
-
It is a directory with a *project file* and a *manifest file*. A *project file* determines what are the names and identities of the direct dependencies of a project. A *manifest file* gives a complete dependency graph, including all direct and indirect dependencies, exact versions of each dependency, and sufficient information to locate and load the correct version.
40
+
It is a directory with a *project file* and a *manifest file*. A *project file* determines what are the names and identities of the direct dependencies of a project. A *manifest file* gives a complete dependency graph, including all direct and indirect dependencies, exact versions of each dependency, and sufficient information to locate and load the correct version.
41
41
42
-
These types of environments provide reproducibility. By storing a project environment in version control, such as a Git repository, along with the rest of project's source code, you can reproduce the exact state of the project and all of its dependencies. The manifest file, in particular, captures the exact version of every dependency, which makes it possible for Pkg to retrieve the correct versions and be sure that you are running the exact code that was recorded for all dependencies. These are fit to run your simulations, and share such files with others.
42
+
These types of environments provide reproducibility. By storing a project environment in version control, such as a Git repository, along with the rest of project's source code, you can reproduce the exact state of the project and all of its dependencies. The manifest file, in particular, captures the exact version of every dependency, which makes it possible for Pkg to retrieve the correct versions and be sure that you are running the exact code that was recorded for all dependencies. These are fit to run your simulations, and share such files with others.
43
43
44
44
***Package Directory**
45
45
46
-
It is directory that contains a tree of subdirectories, and forms an implicit environment. Each subdirectory contains a specific package - if the `X` is is a subdirectory of a package directory and `X/src/X.jl` exists, we can access to the package `X` from the package directory.
46
+
It is directory that contains a tree of subdirectories, and forms an implicit environment. Each subdirectory contains a specific package - if the `X` is is a subdirectory of a package directory and `X/src/X.jl` exists, we can access to the package `X` from the package directory.
47
47
48
-
Package directories are useful when you want to put a set of tools (in the format of packages) somewhere and be able to directly use them, without needing to create a project environment for them.
48
+
Package directories are useful when you want to put a set of tools (in the format of packages) somewhere and be able to directly use them, without needing to create a project environment for them.
49
49
50
50
You can overlay these types of environments and obtained a **stacked environment** - an ordered set of project environments and package directories that make a single composite environment. The precedent and visibility rules combined determine which packages are available and where they get loaded from.
51
51
52
52
## Creating environments and using someone else's project
53
53
54
54
You may have noticed the `(@v1.8)` in the REPL prompt. So far we have added packages to the default environment at `~/.julia/environments/v1.8`. However, it is easy to create independent projects. This let us know that `v1.8` is the **active environment** - this is the environment that will be modified by `Pkg` commands above.
55
55
56
-
To set the active environment, you can use `activate`. In case this, we want to create a new active environment. `Pkg` will let us know that we are creating a new environment that will be stored in your `~/directory`.
56
+
To set the active environment, you can use `activate`. In case this, we want to create a new active environment. `Pkg` will let us know that we are creating a new environment that will be stored in your `~/directory`.
57
57
58
-
````julia
59
-
(@v1.8) pkg> activate MyProject
60
-
Activating new environment at `~/MyProject/Project.toml`
58
+
````julia
59
+
(@v1.8) pkg> activate MyProject
60
+
Activating new environment at `~/MyProject/Project.toml`
61
61
62
-
(MyProject) pkg> st
63
-
Status `~/MyProject/Project.toml` (empty project)
64
-
````
62
+
(MyProject) pkg> st
63
+
Status `~/MyProject/Project.toml` (empty project)
64
+
````
65
65
66
66
Note that the REPL prompt changes when the new project is activated. Until a package is added, there are no files in this environment and the directory of this environment might not be created.
67
67
68
68
When you plan on using someone else's project, you can simply clone their project with `git clone`.
If the project contains a `Manifest.toml` file, this will install the packages in the same state that is given by that manifest. If not, by default the latest versions of the dependencies compatible with the project will be installed.
82
82
@@ -92,22 +92,23 @@ The control **activate** by itself does not install missing dependencies. Howeve
92
92
93
93
The [PkgTemplates.jl](https://github.com/JuliaCI/PkgTemplates.jl) package offers an easy, repeatable, and customizable way to generate files for a new package. Likewise, usage is pretty straightforward. The following example includes the plugins suitable for a project hosted on GitHub, with some other customizations:
94
94
95
-
````julia
96
-
using PkgTemplates
97
-
t = Template(;
98
-
user="my-username",
99
-
dir="~/code",
100
-
authors="author_name",
101
-
julia=v"1.1",
102
-
plugins=[
103
-
License(; name="MPL"),
104
-
Git(; manifest=true, ssh=true),
105
-
GitHubActions(; x86=true),
106
-
Codecov(),
107
-
Documenter{GitHubActions}(),
108
-
Develop()])
109
-
t("MyPkg")
110
-
````
95
+
````julia
96
+
using PkgTemplates
97
+
t =Template(;
98
+
user="my-username",
99
+
dir="~/code",
100
+
authors="author_name",
101
+
julia=v"1.1",
102
+
plugins=[
103
+
License(; name="MPL"),
104
+
Git(; manifest=true, ssh=true),
105
+
GitHubActions(; x86=true),
106
+
Codecov(),
107
+
Documenter{GitHubActions}(),
108
+
Develop()])
109
+
t("MyPkg")
110
+
````
111
+
111
112
Keyword arguments for `PkgTemplates.Template` object type:
112
113
113
114
**User options:*
@@ -121,31 +122,31 @@ Keyword arguments for `PkgTemplates.Template` object type:
121
122
122
123
This creates a new package called `MyPkg`, with a new project `MyPkg.jl` in the subdirectory `src`. You can visualize directory structure with the `tree` command in the Julia REPL.
123
124
124
-
````julia
125
-
shell> tree MyPkg/
126
-
MyPkg/
127
-
├── Project.toml
128
-
├── Manifest.toml
129
-
└── src
130
-
└── MyPkg.jl
131
-
2 directories, 3 files
132
-
````
125
+
````julia
126
+
shell> tree MyPkg/
127
+
MyPkg/
128
+
├── Project.toml
129
+
├── Manifest.toml
130
+
└── src
131
+
└── MyPkg.jl
132
+
2 directories, 3 files
133
+
````
133
134
134
135
The content of `src/MyPkg.jl` can be modified:
135
136
136
-
````julia
137
-
module MyPkg
138
-
#Add functions to the module
139
-
greet() = print(“Hello world!”)
140
-
end
137
+
````julia
138
+
module MyPkg
139
+
#Add functions to the module
140
+
greet() =print(“Hello world!”)
141
+
end
141
142
142
-
pkg> activate ./MyPkg #Activate the project by using the path to the directory where it is installed
143
-
#Load the package
144
-
julia> import MyPkg
145
-
julia> MyPkg.greet()
143
+
pkg> activate ./MyPkg #Activate the project by using the path to the directory where it is installed
144
+
#Load the package
145
+
julia>import MyPkg
146
+
julia> MyPkg.greet()
146
147
147
-
Hello world!
148
-
````
148
+
Hello world!
149
+
````
149
150
150
151
## Styling guidelines
151
152
@@ -155,63 +156,63 @@ Package and function names should be sensible to most Julia users, even to those
155
156
156
157
Back to `Pkg.jl`; let’s say we want to use a standard library package (e.g., `Random`) and a registered package (e.g. `JSON`). We simply add these packages.
157
158
158
-
````julia
159
-
(MyPkg) pkg> add Random JSON
160
-
Resolving package versions...
161
-
Updating `~/MyPkg/Project.toml`
162
-
[682c06a0] + JSON v0.21.3
163
-
[9a3f8284] + Random
164
-
Updating `~/MyPkg/Manifest.toml`
165
-
[682c06a0] + JSON v0.21.3
166
-
[69de0a69] + Parsers v2.4.0
167
-
[ade2ca70] + Dates
168
-
...
169
-
````
159
+
````julia
160
+
(MyPkg) pkg> add Random JSON
161
+
Resolving package versions...
162
+
Updating `~/MyPkg/Project.toml`
163
+
[682c06a0] + JSON v0.21.3
164
+
[9a3f8284] + Random
165
+
Updating `~/MyPkg/Manifest.toml`
166
+
[682c06a0] + JSON v0.21.3
167
+
[69de0a69] + Parsers v2.4.0
168
+
[ade2ca70] + Dates
169
+
...
170
+
````
170
171
171
172
Both `Random` and `JSON` now got added to the project’s `Project.toml` file, and the resulting dependencies to the `Manifest.toml` file. We can now both use Random and JSON in our package, by changing `src/MyPkg.jl` again.
To reload a package in Julia without restarting the Julia session, you can use the [`Revise.jl`](https://timholy.github.io/Revise.jl/stable/) package whenever you want to work interactively and see immediate updates to your code.
185
186
186
-
````julia
187
-
using Revise #Important to load Revise.jl before your package
188
-
using MyPkg
189
-
#Now you can call the updated greet_alien() function
190
-
julia> MyPkg.greet_alien()
187
+
````julia
188
+
using Revise #Important to load Revise.jl before your package
189
+
using MyPkg
190
+
#Now you can call the updated greet_alien() function
191
+
julia> MyPkg.greet_alien()
191
192
192
-
Hello aT157rHV
193
-
````
193
+
Hello aT157rHV
194
+
````
194
195
195
196
## Adding tests
196
197
197
198
To add tests to a package, we have to (1) create a directory for tests, (2) create and write the script file, (3) execute tests.
198
199
199
-
````julia
200
-
#Create path to test directory
201
-
mkpath("MyPkg/test")
202
-
#Create simple test script file in MyPkg/test/runtests.jl
203
-
write("MyPkg/test/runtests.jl",
204
-
"""
205
-
#You can add specific tests here or edit seperatly the file itself
206
-
println("Testing...")
207
-
""");
208
-
#This command will run the tests specified in MyPkg/test/runtests.jl
209
-
(MyPkg) pkg> test
210
-
Testing MyPkg
211
-
Resolving package versions...
212
-
Testing...
213
-
Testing MyPkg tests passed
214
-
````
200
+
````julia
201
+
#Create path to test directory
202
+
mkpath("MyPkg/test")
203
+
#Create simple test script file in MyPkg/test/runtests.jl
204
+
write("MyPkg/test/runtests.jl",
205
+
"""
206
+
#You can add specific tests here or edit seperatly the file itself
207
+
println("Testing...")
208
+
""");
209
+
#This command will run the tests specified in MyPkg/test/runtests.jl
210
+
(MyPkg) pkg> test
211
+
Testing MyPkg
212
+
Resolving package versions...
213
+
Testing...
214
+
Testing MyPkg tests passed
215
+
````
215
216
216
217
## Advanced considerations
217
218
@@ -229,20 +230,20 @@ Before formally registering your package, there are some more advanced nuanced i
229
230
230
231
Use [`ArgCheck.jl`](https://github.com/jw3126/ArgCheck.jl) to perform checks on function arguments for public API (one may also use dedicated types to encapsulate these checks). VPL should have a strong preference for defensive programming even if this comes at the expense of runtime performance (but do try to avoid performance hits and repeating assertions).
231
232
232
-
````julia
233
-
#After installing ArgCheck - Pkg.add("ArgCheck")
234
-
using ArgCheck
235
-
#Example in function
236
-
function f(x,y)
237
-
@argcheck x < y
238
-
#The rest of the function
239
-
end
240
-
241
-
f(0,0)
242
-
ERROR: ArgumentError: x < y must hold. Got
243
-
x => 0
244
-
y => 0
245
-
````
233
+
````julia
234
+
#After installing ArgCheck - Pkg.add("ArgCheck")
235
+
using ArgCheck
236
+
#Example in function
237
+
functionf(x,y)
238
+
@argcheck x < y
239
+
#The rest of the function
240
+
end
241
+
242
+
f(0,0)
243
+
ERROR: ArgumentError: x < y must hold. Got
244
+
x =>0
245
+
y =>0
246
+
````
246
247
247
248
* Try to achieve type stability, by using `@infered` in tests and statistically-size arrays, as often as possible.
248
249
* Avoid containers with abstract types.
@@ -267,29 +268,29 @@ Use [`ArgCheck.jl`](https://github.com/jw3126/ArgCheck.jl) to perform checks on
267
268
All types created for the tests should be included in a module and all tests should be run inside a (let … end) block.
268
269
* Test should cover 100% of source code (at least locally). The following is a template that can be used (normally located in a local dev folder not synced with Github):
269
270
270
-
````julia
271
-
# Run tests and collect statistics on code coverage
272
-
import Pkg
273
-
Pkg.test("PlantRayTracer", coverage = true)
274
-
275
-
# Process the coverage data using Coverage.jl
276
-
using Coverage
277
-
# process '*.cov' files
278
-
coverage = process_folder("src") # defaults to src/; alternatively, supply the folder name as argument
@@ -300,7 +301,7 @@ All changes to a package should occur in a branch and a pull request should be u
300
301
* Significant features have been added
301
302
302
303
Check that all the tests are successful in a dedicated module. This requires running `Pkg.test(<package>)` rather than running the `runtest.jl` file in Julia. This will tell you if there are any dependencies missing in the Project.toml of the tests.
303
-
You may use the development version of a dependency while testing locally, but CI will fail until the changes in the dependency are registered.
304
+
You may use the development version of a dependency while testing locally, but CI will fail until the changes in the dependency are registered.
0 commit comments