Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First steps to allowing rock/rockspec URLs as dependencies, incomplete #823

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

mcclure
Copy link

@mcclure mcclure commented Jun 17, 2018

Here's my first pass. I did a version based on 06a4656, which worked but printed a spurious error in report_missing_dependencies. This new version based against master (I changed the _pattern format, as we discussed) but it does not get as far. It appears to have a similar problem to my 06a4656-based version but more catastrophic. The error is

Error: LuaRocks dev bug (please report at https://github.com/luarocks/luarocks/issues).
...other/luarocks/build/share/lua/5.1/luarocks/deps.lua:23: assertion failed!
stack traceback:
	[C]: in function 'assert'
	...other/luarocks/build/share/lua/5.1/luarocks/deps.lua:23: in function 'match_dep'
	...other/luarocks/build/share/lua/5.1/luarocks/deps.lua:120: in function 'report_missing_dependencies'
	...other/luarocks/build/share/lua/5.1/luarocks/deps.lua:218: in function 'fulfill_dependencies'
	...ther/luarocks/build/share/lua/5.1/luarocks/build.lua:133: in function 'process_dependencies'
	...ther/luarocks/build/share/lua/5.1/luarocks/build.lua:373: in function 'build_rockspec'
	...r/luarocks/build/share/lua/5.1/luarocks/cmd/make.lua:93: in function <...r/luarocks/build/share/lua/5.1/luarocks/cmd/make.lua:56>
	(tail call): ?
	[C]: in function 'xpcall'
	...arocks/build/share/lua/5.1/luarocks/command_line.lua:240: in function 'run_command'
	./build/bin/luarocks:37: in main chunk
	[C]: ?

The problem is match_dep expects the argument, which is pulled out of the dependency table, to be a table and instead it is a string (the http url). This is linked only to the pattern change in rockspec.lua— the error occurs before find_suitable_rock is called, and occurs even if the find_suitable_rock changes are commented out. I'm not sure how this string is getting put in the dependency table. Based on my experience with 06a4656 I think if this is fixed the patch will work.

I can provide my testing files if it would help.

Other issues:

  • It looks like the "fallback", where it notices that you used a 3.0 feature but didn't ask for rockspec_version=3.0, is not working for "dependencies". If I take out my rockspec but put in a URL dependency it does not warn me, it just says it doesn't recognize the dependency.

TODOS:

  • Doesn't support git:// or file:// , only http:// and https://
  • No tests

@codecov-io
Copy link

Codecov Report

Merging #823 into master will decrease coverage by 0.03%.
The diff coverage is 64%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #823      +/-   ##
==========================================
- Coverage   86.23%   86.19%   -0.04%     
==========================================
  Files          92       92              
  Lines        8149     8173      +24     
==========================================
+ Hits         7027     7045      +18     
- Misses       1122     1128       +6
Impacted Files Coverage Δ
src/luarocks/type/rockspec.lua 99.29% <100%> (+0.02%) ⬆️
src/luarocks/type_check.lua 88.13% <100%> (+1.21%) ⬆️
src/luarocks/search.lua 88.88% <37.93%> (-3.38%) ⬇️
src/luarocks/core/util.lua 93.98% <0%> (+0.75%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update bfcb8a9...8e02531. Read the comment docs.

@hishamhm
Copy link
Member

hishamhm commented Jun 20, 2018

@mcclure So I finally had some time to give this a closer look. I found the cause of that weird failure, and pushed 7037d5e, which makes it not crash anymore but moves the error to a different place:

Error: /Users/hisham/projects/luarocks/restserver-xavante-0.3-1.rockspec: Parse error processing dependency 'https://luarocks.org/manifests/mascarenhas/xavante-2.4.0-1.rockspec': Failed to parse constraint ':' with error: Encountered bad constraint operator: 'nil' in ':'

This is a failure in luarocks/rockspecs.lua, as it failed converting the dependencies table from an array of strings into an array of "query" objects (constructed in luarocks/queries.lua) — queries.from_dep_string() needs to learn about URLs-as-dependencies.

This opened a bit of a can of worms: all right, so queries.from_dep_string can be adjusted so that, given an URL, it produces a nice query table (with a name field to make the rest of the code happy). One could parse the URL and extract the proper name and version from the filename even, and possibly store a url field with the full URL in that table, and then check for dep.url in the other modules instead of re-parsing to see if it looks like a URL. So far, so good.

But when we get to deps.report_missing_dependencies... what that function does is: it runs through the list of reported dependencies (now properly converted to table form), and checks if any of them is already installed. To do that, a dependency needs a name and possibly a version (which we were able to extract in the previous paragraph)... but than any version of foo 1.0 that happens to be already installed will satisfy it, even if not the fork from the specific URL you gave. That's might not be what you want!

We have a few options:

  1. any foo is a valid foo: just parse name and version from the URL and put that in the query object. Yes, any foo already-installed will match, even if it's the wrong fork, but when running an isolated project in a project-dir this would be less of a problem.
  2. skip dependency matching altogether: if a dependency is given as a URL, always re-install, it every time the rockspec is built.
  3. try to detect that an installed foo is the same as that URL: this is what the namespaces feature does: if you ask for ns/foo, it will look for a foo that comes from namespace ns. How does it know an installed rock is from a given namespace? it stores a rock_namespace metadata file containing ns in foo's dir. This means that you can't install namespaced and non-namespaced versions of foo at the same time. I decided to live with that limitation (again, with per-project repos, I think that's less of a problem). So, one could take this "namespaces" approach to tell "plain foo" from "URL foo", one of two ways:
    1. doing that trick again creating a rock_url metadata would work, but the code duplication might end up ugly
    2. perhaps storing the URL in rock_namespace file, making the URL double as a pseudo-namespace for that rock could work! I can't think of any places where the code would blow up if one were to try that (famous last words), but if there are those shouldn't be hard to fix.
  4. some other approach I haven't thought of!

Hopefully this makes sense, feel free to ask any questions!

@hishamhm
Copy link
Member

It looks like the "fallback", where it notices that you used a 3.0 feature but didn't ask for rockspec_version=3.0, is not working for "dependencies". If I take out my rockspec but put in a URL dependency it does not warn me, it just says it doesn't recognize the dependency.

Yeah, that's a limitation: the fallback system can only identify new keys, and not existing keys that changed settings. I wonder if it wouldn't be easier/better to just try to typecheck the rockspec with the given format version, and if that fails, try to typecheck again with each later version and any of them succeeds, give an error message like Error in rockspec: field 'foo' is invalid according to rockspec format version 1.0. Adding 'rockspec_format = "3.0"' to the rockspec will fix this.

@mcclure
Copy link
Author

mcclure commented Jun 21, 2018

I wonder if it wouldn't be easier/better to just try to typecheck the rockspec with the given format version, and if that fails, try to typecheck again with each later version and any of them succeeds, give an error message like

I think that would definitely be more user friendly, entirely orthogonal to the URLs issue. I could imagine for example someone getting confused if they try to use namespaces (not allowed in rockspec 1 i think)

We have a few options:

I was originally going to suggest we could treat URLs as versions. However, if packages collide across namespaces (ie if NameA/PackageX and NameB/PackageX can't be loaded at once) then I think your suggestion of treating a URL as a namespace is actually perfect. If I'm understanding the current regime correctly namespaces are more like an "origin" tag than anything else, which is also how the URLs are used. And they even use slashes.

I think we should go with solution 3ii. If this is the approach, maybe should I move the URL parsing to occur earlier (IE, having the URL parsed in the query object?)

Which of us should write the patch for the next step?

@hishamhm
Copy link
Member

I think that would definitely be more user friendly, entirely orthogonal to the URLs issue. I could imagine for example someone getting confused if they try to use namespaces (not allowed in rockspec 1 i think)

That is true (great point about namespaces!). I'll take care of this.

If I'm understanding the current regime correctly namespaces are more like an "origin" tag than anything else, which is also how the URLs are used. And they even use slashes.

Yes, "origin tag" is a great description — I'll quote you on that next time I have to explain LuaRocks namespaces to people! (I thought of several possible designs for adding namespaces to LR, with various degrees of complexity and compatibility issues, which ended up holding me back from implementing for a long time; during a meetup people convinced me to just go with the simplest solution — this one strikes a balance between providing separation for those who use namespaces explicitly and still playing ok with non-namespaced usages because sometimes people do install my_own/foo and want that to act as the main foo in their system)

I think we should go with solution 3ii. If this is the approach, maybe should I move the URL parsing to occur earlier (IE, having the URL parsed in the query object?)

3ii is my favorite as well! Yes, URL parsed in the query object makes the most sense.
I guess given http://example.com/path/foo-1.0-1.rockspec, it should produce something like

{
   name = "foo",
   constraints = { op = "==", version = parsed("1.0-1"), },
   namespace = "http://example.com/path"
   url = "http://example.com/path/foo-1.0-1.rockspec",
}

Then you can special-case behavior based on the presence of the .url field.

Which of us should write the patch for the next step?

I'll take care of the typechecker — could you do the URL parsing and further handling of query.url when matching dependencies?

@mcclure
Copy link
Author

mcclure commented Jun 21, 2018

I'll rebase on 7037d5e, take a look and see if I can push another commit. Thanks!

By the way, the new approach is going to make things a little more confusing when/if we try to add git support.

@hishamhm
Copy link
Member

By the way, the new approach is going to make things a little more confusing when/if we try to add git support.

Yeah, I've been thinking about that. file and http URLs point to rockspecs and rocks, that means it once they are out of find_suitable_rock it's business as usual for LuaRocks.

Now what would a git dependency look like? "git://github.com/user/repo 12ca34fe", I assume? (where the "version" is either a hash or a tag/branch)

That doesn't point to a rockspec very clearly. The luarocks make command does have some logic to autodetect a rockspec inside a repo. So perhaps deps.fulfill_dependency could take a side road when given a git:// dependency and fetch the repo (some tweaking would be necessary to reuse code from luarocks.fetch.git) and invoke make.command instead. A little tricky, but doable.

@hishamhm
Copy link
Member

(git support is more forward-thinking stuff, I think — I'd limit the scope of this PR to file:// and http(s):// only — after all today you can do luarocks install http://...rockspec but you can't do luarocks install git://...)

@mcclure
Copy link
Author

mcclure commented Jun 22, 2018

I think a git dependency would need 3 pieces of information included: a git repo, a git revision, and a path to a rockspec file. I assume we'd have to separate them by spaces. I found one open source tool that uses // to indicate a file in a repo, ie, domain.com/gitrepo.git//path/to/file.txt , but that is hardly standard. I definitely think it makes more sense to get the file:// https:// change in first and then worry about git afterward. Of course for github there's always blob urls…

@mcclure
Copy link
Author

mcclure commented Jun 26, 2018

New commit allows use of http and https dependencies in my tests. Next step, file:// , then I'll try to do some tests.

@mcclure
Copy link
Author

mcclure commented Jun 26, 2018

Oh, I didn't populate the namespace field of "query". I left it nil. Is there logic that that is inappropriate for?

@codecov-io
Copy link

codecov-io commented Jun 26, 2018

Codecov Report

❗ No coverage uploaded for pull request base (master@f893acf). Click here to learn what that means.
The diff coverage is 60.65%.

Impacted file tree graph

@@            Coverage Diff            @@
##             master     #823   +/-   ##
=========================================
  Coverage          ?   86.75%           
=========================================
  Files             ?       90           
  Lines             ?     8885           
  Branches          ?        0           
=========================================
  Hits              ?     7708           
  Misses            ?     1177           
  Partials          ?        0
Impacted Files Coverage Δ
src/luarocks/type/rockspec.lua 99.35% <100%> (ø)
src/luarocks/type_check.lua 89.18% <100%> (ø)
src/luarocks/search.lua 88.88% <37.93%> (ø)
src/luarocks/queries.lua 90.59% <53.84%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f893acf...a93a097. Read the comment docs.

@mcclure
Copy link
Author

mcclure commented Jun 26, 2018

A couple more things.

  1. Looking closer at my test (a rock named "pseudocpml" which includes-by-url a dep named "cpml", I discover that running my test results in messages of this sort
Warning: /Users/mcc/work/gh/other/luarocks/test-andi/output/share/lua/5.1/cpml/init.lua is not tracked by this installation of LuaRocks. Moving it to /Users/mcc/work/gh/other/luarocks/test-andi/output/share/lua/5.1/cpml/init.lua~~~~~~~
Warning: /Users/mcc/work/gh/other/luarocks/test-andi/output/share/lua/5.1/cpml/modules/quat.lua is not tracked by this installation of LuaRocks. Moving it to /Users/mcc/work/gh/other/luarocks/test-andi/output/share/lua/5.1/cpml/modules/quat.lua~~~~~~~
Warning: /Users/mcc/work/gh/other/luarocks/test-andi/output/share/lua/5.1/cpml/modules/color.lua is not tracked by this installation of LuaRocks. Moving it to /Users/mcc/work/gh/other/luarocks/test-andi/output/share/lua/5.1/cpml/modules/color.lua~~~~~~~
Warning: /Users/mcc/work/gh/other/luarocks/test-andi/output/share/lua/5.1/cpml/modules/constants.lua is not tracked by this installation of LuaRocks. Moving it to /Users/mcc/work/gh/other/luarocks/test-andi/output/share/lua/5.1/cpml/modules/constants.lua~~~~~~~

One for each file in cpml. Do you know what this might mean?

  1. I would like to add some ability for a dependency to be specified "relatively". That is, say one source repo contains several rock files (for organizational reasons or perhaps because there is a mix of build types). I can think of two ways of doing this:
  • file:// URLs beginning with "."
  • specifying the file as "./path/to/file.rockspec". This implies banning the namespace ".", which might already be invalid.

At the moment, file:// URLs may begin with . but the path taken is relative to the pwd which luarocks was executed from. So the change here would be to change the pwd interpreted in deps.fulfill_dependency to be the directory the including dep is found in.

The "./" approach might be better though because it would work for non-local rockspecs (for example, rock A has dependency https://blah/BLAH.rockspec and BLAH has dependency ./OTHER.rockspec).

What do you think, is this feature worth bothering with?

@hishamhm
Copy link
Member

Oh, I didn't populate the namespace field of "query". I left it nil. Is there logic that that is inappropriate for?

It won't crash (nil means "no namespace"), but since in solution 3ii we want to store the URL as a namespace for the rock, you can store dir.dir_name(url) in it.

I discover that running my test results in messages of this sort

Odd. This means that those files are not being properly stored in the manifest. Please take a look at /Users/mcc/work/gh/other/luarocks/test-andi/output/lib/lua/rocks-5.1/manifest and see if the files are correctly referenced there. They should appear both in the modules table (which maps module names to rocks) and in the repository table (which maps rocks to module names to filenames). This would be our first step debugging this.

Also, if you delete your output directory and start over, does this still happen?

I would like to add some ability for a dependency to be specified "relatively".

At the moment, file:// URLs may begin with . but the path taken is relative to the pwd which luarocks was executed from.

That would generally work for luarocks make and per-project workflows at the "top level", but probably not when resolving nested dependencies unless they're all in the same project.

So the change here would be to change the pwd interpreted in deps.fulfill_dependency to be the directory the including dep is found in.

...which could be tricky (or easy to implement), I don't know.

Of the two approaches, I like file://. better because it less "new" syntax.

What do you think, is this feature worth bothering with?

It does sound like an edge case. I'd welcome it if it doesn't add a lot of complexity.

@mcclure
Copy link
Author

mcclure commented Jun 27, 2018

“since in solution 3ii we want to store the URL as a namespace for the rock, you can store dir.dir_name(url) in it”

I tried adding this (setting the namespace to the URL-minus-filename, as you suggested).

It resulted in this failure

Error: LuaRocks dev bug (please report at https://github.com/luarocks/luarocks/issues).
...other/luarocks/build/share/lua/5.1/luarocks/deps.lua:178: assertion failed!
stack traceback:
	[C]: in function 'assert'
	...other/luarocks/build/share/lua/5.1/luarocks/deps.lua:178: in function 'fulfill_dependency'

The failure occurs because in match_dep, "versions" is an empty array for the URL query and match_dep returns nil.

Do you know why?

Also purely because I'm curious, what code is supposed to be reading that namespace back out, once it is placed in the query object, since search.find_suitable_rock is now bypassing it?

"...which could be tricky (or easy to implement), I don't know."

I think it would be easy and I think I know how to do it, but let's consider it out of scope for this PR. I'll make a new PR and see if I can make it make sense

@mcclure
Copy link
Author

mcclure commented Jun 27, 2018

Also would it be possible to get some assistance with creating a test in the spec/ folder? It seems the tests are done in a particular way.

All we need for the test is to have a local rockspec that has the https address of the github blob for one of the rockspecs used in the other tests, then add a test in spec/make_spec.lua to make sure the dependency test is installed. I think a file:// test is not possible without relative paths. Here's the file I've been using for local tests:

pseudopseudocpml-a-1.rockspec.txt

I think this PR can be possibly merged once the namespace and "is not tracked by this installation of LuaRocks" issues are resolved, and once we have a test in spec/? Is there anything else to be done?

hishamhm added a commit that referenced this pull request Jul 2, 2018
* luarocks/type_check.lua:
  * fix expansion of platforms
* luarocks/type/rockspec.lua:
  * when a version fails checking, try with a later
    version to produce a hint in the error message.

See #823.
hishamhm added a commit that referenced this pull request Jul 2, 2018
* luarocks/type_check.lua:
  * fix expansion of platforms
* luarocks/type/rockspec.lua:
  * when a version fails checking, try with a later
    version to produce a hint in the error message.

See #823.
hishamhm added a commit that referenced this pull request Jul 2, 2018
* luarocks/type_check.lua:
  * fix expansion of platforms
* luarocks/type/rockspec.lua:
  * when a version fails checking, try with a later
    version to produce a hint in the error message.

See #823.
@hishamhm
Copy link
Member

hishamhm commented Jul 2, 2018

what code is supposed to be reading that namespace back out, once it is placed in the query object

manif.get_versions.

The failure occurs because in match_dep, "versions" is an empty array for the URL query and match_dep returns nil.

Do you know why?

Not sure. I haven't run your branch locally to test yet, but install.command({deps_mode = deps_mode, namespace = dep.namespace}, url) there should have associated the namespace to the URL so that match_dep would find it later...

I wrote a quick test here to check that URLs and the --namespace flag play nicely with each other and that passed in current master

diff --git a/spec/install_spec.lua b/spec/install_spec.lua
index 8a3ede3..0975a58 100644
--- a/spec/install_spec.lua
+++ b/spec/install_spec.lua
@@ -98,6 +98,14 @@ describe("luarocks install #integration", function()
          assert(lfs.attributes(testing_paths.testing_sys_rocks .. "/a_rock/2.0-1/rock_namespace"))
       end)
 
+      it("installs a namespaced package given an URL and any string in --namespace", function()
+         assert(run.luarocks_bool("install --namespace=x.y@z file://" .. testing_paths.fixtures_dir .. "/a_rock-1.0-1.src.rock" ))
+         assert.truthy(run.luarocks_bool("show a_rock 1.0"))
+         local fd = assert(io.open(testing_paths.testing_sys_rocks .. "/a_rock/1.0-1/rock_namespace", "r"))
+         finally(function() fd:close() end)
+         assert.same("x.y@z", fd:read("*l"))
+      end)
+
       it("installs a package with a namespaced dependency", function()
          assert(run.luarocks_bool("install has_namespaced_dep --server=" .. testing_paths.fixtures_dir .. "/a_repo" ))
          assert(run.luarocks_bool("show has_namespaced_dep"))

I just merged a big branch — including the rockspec format version checking changes we discussed, which is the cause of the current merge conflict — so not sure if that affected the above problem some way. When you get your branch in sync I'll fetch and run it locally to see if I can spot anything related to the namespace issue.

@hishamhm
Copy link
Member

hishamhm commented Jul 2, 2018

Also would it be possible to get some assistance with creating a test in the spec/ folder? It seems the tests are done in a particular way.

Yeah, the test suite is very, erm, peculiar, as it has a long history behind it (started as a big shell script, ported to Lua — it's currently being improved a lot by @georgeroman as his GSoC project!)

All we need for the test is to have a local rockspec that has the https address of the github blob for one of the rockspecs used in the other tests, then add a test in spec/make_spec.lua to make sure the dependency test is installed.

We're now trying to write new tests so that they don't rely on live internet URLs. We do launch a tiny HTTP server in the test suite so http://localhost:8080/file/ serves files in spec/fixtures (it also mocks some responses of luarocks.org).

This is an example of a test using it:

https://github.com/luarocks/luarocks/blob/master/spec/build_spec.lua#L437-L442

It uses this file:

https://github.com/luarocks/luarocks/blob/master/spec/fixtures/with_external_dep-0.1-1.rockspec

Tests using the HTTP server should be inside a describe block with the #mock hashtag, and need this setup-teardown sequence:

https://github.com/luarocks/luarocks/blob/master/spec/build_spec.lua#L394-L402

I think a file:// test is not possible without relative paths.

We can write a rockspec on the fly, like this:

https://github.com/luarocks/luarocks/blob/master/spec/build_spec.lua#L299-L322

@mcclure
Copy link
Author

mcclure commented Jul 8, 2018

Rebased the commits and fixed the merge conflicts.

Still seeing the failure (but only when I include the commit which handles namespaces correctly).

Here is my test case w/instructions
spec-test.zip

Here is the error I get

cpml scm-1 is now installed in /Users/mcc/work/gh/other/luarocks/spec-test/output (license: MIT)


Error: LuaRocks dev bug (please report at https://github.com/luarocks/luarocks/issues).
...other/luarocks/build/share/lua/5.1/luarocks/deps.lua:178: assertion failed!
stack traceback:
	[C]: in function 'assert'
	...other/luarocks/build/share/lua/5.1/luarocks/deps.lua:178: in function 'fulfill_dependency'
	...other/luarocks/build/share/lua/5.1/luarocks/deps.lua:217: in function 'fulfill_dependencies'
	...ther/luarocks/build/share/lua/5.1/luarocks/build.lua:139: in function 'process_dependencies'
	...ther/luarocks/build/share/lua/5.1/luarocks/build.lua:379: in function 'build_rockspec'
	...r/luarocks/build/share/lua/5.1/luarocks/cmd/make.lua:94: in function <...r/luarocks/build/share/lua/5.1/luarocks/cmd/make.lua:57>
	(tail call): ?
	[C]: in function 'xpcall'
	.../other/luarocks/build/share/lua/5.1/luarocks/cmd.lua:427: in function 'run_command'
	./build/bin/luarocks:35: in main chunk
	[C]: ?

The "assert" is that match_dep returns nil
The dep that does not match is https://raw.githubusercontent.com/mcclure/cpml/master/cpml

Can you take a look at this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants