How to use VSCode and REPL to write and test a package?

Yes, that can happen. Though I don’t remember the last time I had these kind of problems if I keep Plots up to date. That is part of what I’m trying to convey, that although these issues can happen, I am not sure if they are frequent enough to justify much more environment management complications. But of course that will depend on the specific packages and dependencies each one is dealing with.

1 Like

How would I possibly figure out to add that to my startup.jl?

You are suggesting yet another entirely new workflow and external package from those already suggested, so yes it is complicated to know what to use.

  • Is main defined in runtests.jl, MyPackage.jl, or myfile.jl?
  • How is Infiltrator different from the built-in VSCode “Run and Debug”?
  • I need to work more interactively than to test all my code at once. It usually takes me three tries to write one line correctly. Can you test line-by-line with this workflow?
1 Like

I will try this out today. Thanks.

Yes, this comes up in benchmarking as well. @davidanthoff I think this is another useful feature.

Basically, if someone wanted to activate an environment with subfolder other than test, then do the equivalent of ] activate subfolder and then ] dev . where . is the path to the main project file.

Sadly I don’t think it is enough for the test environment? @oxinabox any ideas on whether the ] dev . after ] activate test is enough in some cases? The code in https://github.com/JuliaTesting/TestEnv.jl/blob/release-1.8/src/activate_set.jl does lots so I don’t have a mental picture of whether some of it is for corner cases.

1 Like

100% agree. I feel that the least-painful approach is to only keep packages in the main environment which should NEVER show up as direct dependencies, and don’t have too extreme of dependencies themselves. So mine is never more complicated than just doing the following with a new global environment.

] add Revise TestEnv Infiltrator BenchmarkTools PkgBenchmark

Everything else I keep in reproducible package/manifests - which never depend on those directly since they are development tools.

2 Likes
  • Define it in either MyPackage.jl or myfile.jl. I said in my post that it should be defined by the Package. i.e., the code that is run when you do using MyPackage.
  • I don’t know. If you are comfortable with VS Code’s “Run and Debug” then use that.
  • One option is to just have a run_tests() function defined in some script that’s being tracked and copy-and-paste code as needed into that function depending on what part you are working.

My point is that if you are asking for a “blessed” workflow, you are going to get tons of different answers. Julia is flexible, but with minimal knowledge of how workflows are in the abstract you can find something that works for you.

1 Like

Not sure. I think using the REPL for package operations is tough to bypass. Too many awesome features integrated deeply into Julia.

I feel like this may not be something that could be worked around with tooling entirely. Seems like the package manager really needs to implement a feature like TestEnv (e.g., Pkg.activate_test("test") or Pkg.activate("test"; activate_as_test = true) or whatever. Then package operations could still point at test/Project.toml, reset the manifest it uses, etc. I just don’t understand what is special about test projects (outside of doing a ] dev . afterwards).

Dreaming of related features that address @ianfiske points, it would be great to have something which automatically does that last step for non-test project files. For example a ] activate benchmark --dev_parent or Pkg.activate("benchmark"; dev_parent = true) does ] activate benchmark then ]dev . I would say that the ] dev . even makes sense as the default behavior.

I feel this would be even more confusing, as it is a) specific to VS Code (not built-in to Julia) and b) doesn’t address the problem that @ianfiske mentioned below. Fundamentally, the only clean way I see has at least 3 different environments:

  1. The main package environment “main
  2. The test environment “test
  3. At least one environment for actually using the package “usage”. Realistically we’ll need at least two environments, one internal to the package that an end-user will see on github and one for the developer that lives in a 2nd git repository.

The main package environment should contain the bare minimum of dependencies that are needed to use the package, e.g. no test dependencies and no interactive usage dependencies. We don’t want these in the main package because they might cause conflicts with other dependencies a user of the package might have.

The test environment should have everything that is needed to (unit) test the package, but nothing more. It should not have e.g. plotting dependencies if plotting is not part of the unit tests.

The usage environment is an example of how the package is used. This is where all extra user-level dependencies are, e.g. plotting and data examples. These dependencies are not needed for unit testing, so they should not go to the test environment, as they might stop an otherwise good test from completing.

There can be more than one usage environment. For a registered package, it would be nice to have an example usage environment that comes with the package. This usage environment is complementary to the test environment and geared towards documenting the behavior of the package. It might contain interactive notebooks that don’t make sense for unit tests, and we don’t want the tests to fail because of an issue with the interactive notebooks that an end user might never need.

On the other hand, a developer might not want to share all of the use cases of his package, they will probably also contain proprietary data. For example, if I develop a package to analyze data, I will do so using data that I cannot check into the repository of the package itself because it is a) to big or b) proprietary, but later I might add a couple internal examples to the package using sanitized data. An end user of myPackage then has 3 ways to use the package: 1. import 2. test 3. run the examples, probably in an interactive notebook.

I use the following workflow:

  1. Create the myPackage with an integrated test environment
  2. Create a 2nd project myPackage_examples that uses Revise and the package above. All interactive exploring, testing etc. is done in this project.

I also find the overall situation very confusing. What I would wish for is:

  1. Better documentation for the use-case that I described above Workflow Tips · The Julia Language doesn’t work for me, and I am not even convinced that my approach is the way to go.
  2. A community-agreed on workflow and some automation (i.e.
    PkgTemplates.jl
    could automatically generate a 2nd project folder and repo myProject_examples that uses Revise and myProject
  3. From the VS Code side, it would be great if using the example project was transparent. If I am working on myPackage, I will have editor windows open for myPackage and myPackage_examples, but the repl should run in myPackage_examples.
  4. A way to add internal examples with a separate environment to a package, similar to tests (maybe this is possible with nested environments, I haven’t tried).

Point 4. will help a user when trying out a new package.
Let’s say I find greatPackage.jl on github and want to see how it works. In order not to contaminate my main environment, I need to first create a new project and add the package and all the dependencies that are needed to actually use the package. These are not dependencies of the package itself, but additional ones like plotting, so I have to do it manually. It would be much nicer if a package had an internal example folder with an example environment and a couple examples that could easily be cloned into a new project in my dev folder.

6 Likes

Another idea could be to introduce a new repl target as a standard for packages (or maybe example?). So a Project.toml for a package might look like this:

name = "MyPackage"
uuid = "4e02106f-b1d8-42ee-a8ca-a30ff59aa51b"
version = "1.0.0"

[deps]
Tokenize = "0796e94c-ce3b-5d07-9a54-7f471281c624"

[compat]
Tokenize = "0.5.18"
julia = "1"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"

[targets]
test = ["Test"]
repl = ["DataFrames"]

So now, when you open a folder with a file like that in the root in VS Code, and you open a REPL, we could activate a “virtual” project that has MyPackage deved, and then DataFrames is in there too, but not Test.

One can kind of emulate some of these things today by for exampling creating a project in an example folder, then deving the relative ./.. path into that project, and then adding whatever else one wants. The Problem with that approach for the VS Code extension is that this is now just some random project in a sub folder, and so we don’t really have any signal that we should auto-activate that project when we start a REPL…

I general, the big issue Is that all new users, and not so new, will always add packages to Main by default. IMHO how Main actually works is what is the problem.

Maybe Julia should always start in a new temporary type of environment that responds to “using” by adding the latest package version locally available.

If the user activated manually any other environment, the current behavior is fine.

I know from other discussions that this is not simple, but the truth is that I don’t know of any package that suggests the user to deal with environments in any way before installing. Thus bloating the Main is and will be the natural workflow.

1 Like

What do you mean by Main? The global default v1.7 project? The package project when you activate it? The term “main” is not usually used for any of these :slight_smile:

1 Like

I guess that one (although I don’t know of it being particular to 1.7 in any sense, so maybe that’s not it).

I mean the environment that is available when one starts Julia without any other flag. Which is special by the fact that one can use packages that are installed in it from other environments.

I have Plots installed there and I don’t find any other alternative reasonable. And Plots only already adds lots of stuff there. Also I have the impression that not recommending installation of packages in the default environment creates artificial constraints for the development of these packages. So for example the developers of BenchmarkTools have to consider that their package is a likely candidate for being in the default environment of everyone in their design choices. That doesn’t seem reasonable to me.

Here is an example of a typical workflow that probably many people implement manually. I have not found much documentation describing this flow, are there better solutions? Any suggestions how to approach this more effectively are highly welcome.

Developer Alice creates a package, but neither wants to share her internal development code, nor does she want to pollute her package with the dependencies she uses for development.

Bob is an end-user, who will run the examples provided by Alice. These examples have dependencies beyond those of Alice’s package and Bob would like to have a simple way of instantiating the examples without modifying Alice’s package itself.

Alice is tasked with writing an analysis package MyPkg that she will test using BigSecretDataset. Ultimately, MyPkg will be released to the public, but BigSecretDataset won’t.

Developer Alice starts by creating MyPkg using a modified PkgTemplates. Her package will have tests, example notebooks and a separate project that she will use for development and not share with others.

This is Alice Developer’s workflow:

Alice creates a package with PkgTemplates, currently the # options are not supported:

julia> Template(interactive=true)("MyPkg")
Template keywords to customize:
[press: d=done, a=all, n=none]
 > [ ] user
   [ ] authors
   [ ] dir
   [ ] host
   [ ] julia
   [ ] plugins
# create a sub-folder examples in MyPkg package and populate with example usage
   [X] create example folder with new environment 
# create a 2nd project repo that is for the internal use of the developer
   [X] create separate project repo for development

...

PkgTemplates creates MyPkg as before, but it also creates

  • a project MyPkg_dev that uses Revise and MyPkg. MyPkg_dev is a separate folder that has its own git repo.
  • a subfolder examples in MyPkg that has its own environment, just like the tests folder.

Alice starts writing code in MyPkg/src/MyPkg.jl. Tests are added to MyPkg/test/runtests.jl, just like in any other package. tests also contains the test-only dependencies.

Interactive development takes place in MyPkg_dev. This is where she will copy BigSecretDataset and put code that is not meant for end users.

VS Code should run any interactive repl in MyPkg_dev. Probably this would need to be a manual setting in VS Code on Alice’s machine, because MyPkg_dev is not meant to be shared with the world. If we add a repl target option to Project.toml, it will point to an empty folder on user Bob’s machine.
VS Code could recognize a project MyPkg_dev and ask if this should be used as the development environment.

Alice puts a couple of simplified examples into MyPkg/examples/examples.ipynb.
Just like MyPkg/test, MyPkg/examples has its own dependencies that do not pollute the package MyPkg.

This is Bob User’s workflow:

Ultimately, MyPkg is released to the public. Bob can now:

  • pkg> add MyPkg # adds the package, nothing new
  • pkg> dev MyPkg # for making changes to dev itself
  • pkg> test MyPkg # run the tests in the test environment
  • pkg> explore MyPkg # Doesn’t exist yet: prepare a project with the right dependencies to run the examples in MyPkg

If Bob wants to just use MyPkg, he uses add, as he would today. If he wants to create a better version of MyPkg, he uses dev. Using dev is fine for development, but not for exploring the examples of MyPkg, because then Bob would need to maintain his own version of the package and manually keep track of the upstream changes. Also Bob does not want MyPkg and the examples’ dependencies in his Main environment. This is why the new command explore is needed.

explore creates a project ~bob/.julia/dev/MyPkg_explore and copies all of the examples of MyPkg/examples to this folder and adds the example dependencies.
Bob can now change the examples as he desires and still update MyPkg without merging. He won’t get any updated examples merged into his MyPkg_explore project, but that is only a minor issue and it makes sense to not overwrite Bob’s example code.

Does this make sense? Is anyone else following a similar workflow by manually creating a 2nd _dev project for each package they develop and an _explore project for each package they want to explore?

5 Likes

startup.jl is mentioned in the docs here. You just create a file called startup.jl and put it ~/.julia/config. where ~/.julia is the same folder you told VS Code to point to when you set up Julia on VS Code.

Thanks, but I meant that question colloquially and rhetorically. I was trying to say that no beginner would come up with that function definition on their own. There needs to be an easier way to develop packages without running into dependency and definition issues and without having to define your own custom functions to make development go smoothly.

I agree! But I should also note that it’s not necessary. I developed packages for 2 years without using this function.

There are a lot of lifestyle improvements that are needed for introductory development of packages.

I need some more clarification on using a local package and its dependencies in the test environment.

julia> using TestEnv

(@v1.7) pkg> activate MyPackage
  Activating project at `C:\Users\nboyer.AIP\.julia\dev\MyPackage`

julia> TestEnv.activate()
Precompiling project...
  1 dependency successfully precompiled in 1 seconds (3 already precompiled)
"C:\\Users\\nboyer.AIP\\AppData\\Local\\Temp\\jl_nPSgZl\\Project.toml"

julia> using MyPackage

julia> f([3,4])
10.0

julia> norm([3,4])
ERROR: UndefVarError: norm not defined
Stacktrace:
 [1] top-level scope
   @ REPL[49]:1

julia> using LinearAlgebra

julia> norm([3,4])
5.0

(jl_nPSgZl) pkg> status
      Status `C:\Users\nboyer.AIP\AppData\Local\Temp\jl_nPSgZl\Project.toml`
  [4b534a9b] MyPackage v0.1.0 `C:\Users\nboyer.AIP\.julia\dev\MyPackage`
  [37e2e46d] LinearAlgebra `@stdlib/LinearAlgebra`
  [8dfed614] Test `@stdlib/Test`

julia> using Plots
[ Info: Precompiling Plots [91a5bcdd-55d7-5caf-9e0b-520d859cae80]

(jl_nPSgZl) pkg> status
      Status `C:\Users\nboyer.AIP\AppData\Local\Temp\jl_nPSgZl\Project.toml`
  [4b534a9b] MyPackage v0.1.0 `C:\Users\nboyer.AIP\.julia\dev\MyPackage`
  [37e2e46d] LinearAlgebra `@stdlib/LinearAlgebra`
  [8dfed614] Test `@stdlib/Test`

julia> plot([1:3],[4:6])
[ Info: Precompiling GR_jll [d2c73de3-f751-5644-a686-071e5b155ba9]
  1. Why does f work (using norm internally) but norm doesn’t until I using LinearAlgebra?
  2. Why am I allowed to use Plots even though it is only in my global 1.7 environment, not my test environment? I thought TestEnv was supposed to restrict this?
  3. Why are only some of DifferenceEquations/Project.toml dependencies repeated in DifferenceEquations/test/Project.toml? Likewise, why are only some DifferenceEquations dependencies using-ed in DifferenceEquations/test/runtests.jl?
  4. Apparently I am not supposed to ] dev . in my test environment if using the TestEnv approach: link?

If I take this approach instead, is compatibility ensured by manually copying the [compat] from MyPackage/Project.toml to MyPackage/test/Project.toml whenever it changes?

TLDR: I understand that test-only dependencies can be kept in a separate test environment, but I don’t understand how to call the main package and its dependencies from the test environment (both Project.toml files and using statements).

Also, the environment seems to not be respecting my [compat] entries.
image

Same reason as with normal Julia packages and scoping? You probably had a using LinearAlgebra within the package itself. The test environment is orthogonal to that.

Not sure that is an advertised feature. It helps take care of test only dependencies but can’t circumvent environment stacking. So I would add all test only dependencies you need and not rely on stacking…which wouldn’t work with CI (and I don’t think it works with ] test though could be wrong on that last part.

Regardless, I think the workflow is probably easier

Anything that the tests need to access directly needs to be a test only dependency. There is no automatic exporting from packages of their own dependencies. This is true for all unit tests, not just with TestEnv

No, don’t do that. It should all work with only that simple activation workflow I gave. If it doesn’t then chances are that the ] test wouldn’t either.

Stacked dependencies in your “global” environment could be making things difficult to follow but their is no reason they can’t coexist if you know what you are doing. Personally, I always avoid having globally available non-dev tool dependencies myself because I find it easier to avoid confusing myself and it is easier to have environments completely separated and have manifest snapshots which recreate everything as required.

That doesn’t look like the instantiated test dependency file to me? Though maybe I am missing something. If you do the TestEnv.activate() or ] test it should instantiate an environment which respects bounds you set.