This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
ElvishJerricco's profile picture

Grin-Get - A Grin based Package Manager

Started by ElvishJerricco, 03 October 2014 - 04:07 AM
ElvishJerricco #1
Posted 03 October 2014 - 06:07 AM
Grin-Get - Package Manager


I recently created a tool called Grin. It's a lightweight interface to downloading releases from GitHub. Grin is nice because it lets developers create complex projects without worrying about complex installation processes for users. Instead all that's needed is a zip file of what's supposed to be installed on the user's computer. Then you make an installer that invokes Grin (through pastebin) with the proper arguments and Grin takes care of downloading, decompressing, and saving the installation to disk.

Grin-Get is a package manager based on Grin. Unlike Grin, which is meant to be lightweight and transparent to the user, Grin-Get is an interface for the user that manages the installation of packages and the interactions between them.

Github
Pastebin installer: HGAmBJiN

Usage
grin-get {install : remove} …
  • grin-get install {package}
  • This command is used to install a package. Packages are denoted by the username and repo name for the project on GitHub. e.g.
    grin-get install Team-CC-Corp/Grin
    By default, grin-get installs the latest version of the package, but this can be changed by specifying a version, like this:
    grin-get install Team-CC-Corp/Grin/1.0.1
    Grin-Get can install any Grin compatible release. No special action is needed from developers if the developer wants Grin-Get support on top of their Grin support. This is nice because it's already easy and powerful for a developer to support Grin, and this way they can support installation independently of Grin-Get. So they can keep their installation process transparent to the user if they don't want to require Grin-Get, while still supporting Grin-Get.
  • grin-get remove {package}
  • This is rather straightforward. It simply deletes the package from the packages folder.
Opt-In Features
Grin-Get does provide some features developers might want, but these features only work through Grin-Get, not through ordinary Grin. Packages that make use of these features require installation via Grin-Get.
  • grin.json
  • Spoiler
      Grin-Get packages can include a grin.json file. This should represent an object with several optional fields.
    • bin
    • The bin field can be a string delimited by colons, where each section is a path in the package where executables are found. Or the bin field can be a table with strings that represent the same things.
    • dependencies
    • The dependencies field is like the bin field, except pacakges that this package is dependent on are found instead of executables.
    • lib
    • The lib field is like the bin field, except APIs are found instead of executables.
    • help
    • The help field is like the bin field, except help files are found instead of executables.
    • startup
    • This should be a string that points to the file the package wants to run at startup. Example grin.json file:
      
      {
      	"bin": "programs",
      	"lib": "apis",
      	"help": "docs",
      	"startup": "load.lua"
      }
      
  • The grin API
  • Spoiler
      Utility Functions:
    • function grin.combine(path, …)
    • Returns the fs.combine() of a variable number of components
    • function grin.assert(condition, errMsg, level)
    • Just like assert but with a level parameter like error
    • function grin.expect(t, v)
    • Asserts that type(v) == t Grin Functions: Package names are in the format user/repo/version If version isn't supplied, it is determined to be the latest installed version.
    • function grin.setGrinDir(dir)
    • Sets the directory that grin knows as its root
    • function grin.getGrinDir()
    • Gets the root grin directory
    • function grin.packageNameComponents(pkg)
    • Returns the user, repo, version of the given full package name.
    • function grin.refreshPath(shell)
    • Puts the bin folders of all grin-get packages on the path of the given shell.
    • function refreshHelpPath()
    • Puts the help folders of all grin-get packages on the help path.
    • function grin.resolvePackageRoot(pkg)
    • Get the root directory of the given package.
    • function grin.getPackagePathItems(pkg)
    • Returns a table of all the bin folders that a package specifies.
    • function getPackageHelpItems(pkg)
    • Returns a table of all the help folders that a package specifies.
    • function grin.getPackageLibItems(pkg)
    • Returns a table of all the lib folders that a package specifies.
    • function grin.resolveInPackage(pkg, path)
    • Returns the full path of the file found at the given path in the given package's bin folder(s).
    • function grin.getFromPackage(pkg, path)
    • Returns the combination of the given package's full path and the given path.
    • function grin.getPackageGrinJSON(pkg)
    • Returns the object represented in the given package's grin.json file.
    • function grin.getReleaseInfo(pkg)
    • Returns the object represented by releases.json in the repo's directory. Will resort to GitHub if necessary.
    • function grin.getReleaseInfoFromGithub(pkg)
    • Returns the object represented by the json GitHub returns for the given repo.
    • function grin.getLatestInstalledVersion(pkg)
    • Returns the tag name of the latest installed version of the package.
    • function grin.forEach(func)
    • Calls the given function once for each installed package Passes the package name as a parameter. Does not call the function multiple times for different versions of the same package
    • function grin.isPackageInstalled(pkg)
    • Checks if the given package is installed.
    • function grin.packageFromExecutable(path)
    • Given the full path of a program, returns the package that program resides in.
    • function grin.getPackageAPI(pkg, name)
    • Finds the API for the given name in the given package. Then loads that API with os.loadAPI Returns the loaded API. Ignores .lua file extensions, which is nice for external editors.
Installation
Grin-Get is installed via a pastebin based Grin installer. Just run:

pastebin run HGAmBJiN
And Grin-Get should be installed. But you need to add something like the following to your startup file

shell.run("grin/bin/grin-startup.lua")
Because Grin-Get needs to do a couple things as the computer starts up to allow you to use your packages easily.
Edited on 03 December 2014 - 05:17 AM
Mr. Bateman #2
Posted 03 October 2014 - 08:12 AM
Seems pretty neat. It's a shame though that the community isn't large enough to keep the momentum with package managers.
ElvishJerricco #3
Posted 03 October 2014 - 03:14 PM
Seems pretty neat. It's a shame though that the community isn't large enough to keep the momentum with package managers.

Yea. That's why I think quick and easy installation is essential. Grin is the main idea here, since that's completely transparent and users don't even have to realize they're using it. But Grin-Get was just the logical evolution.
ElvishJerricco #4
Posted 07 October 2014 - 09:45 AM
Updated. Changes:
  • Added grin.packageFromExecutable – gets the package name from an executables full path.
  • Added support for help paths.
  • Startup files are run in parallel.
  • Support a special "dev" version of a package. Use for development of a Grin-Get dependent package.
ElvishJerricco #5
Posted 10 October 2014 - 12:22 PM
Update. Changes:
  • Fixed getPackageAPI
  • Updated Grin version
  • Error instead of printError() return;
ElvishJerricco #6
Posted 12 October 2014 - 06:59 PM
Updated. Changes:
  • Added __package variable in API environments so they can identify their packages.
ElvishJerricco #7
Posted 04 November 2014 - 05:42 AM
Updated. Bug fix
ElvishJerricco #8
Posted 03 December 2014 - 06:18 AM
Updated: Transferred ownership to the new team Yevano and I have started.
Phoenix323 #9
Posted 16 December 2014 - 01:03 AM
i just keeps looping and looping over and over
ElvishJerricco #10
Posted 16 December 2014 - 06:02 AM
i just keeps looping and looping over and over

Going to need more detail than that. Also note that larger packages (JVML is very large) can take some time to unarchive.
Bomb Bloke #11
Posted 11 April 2015 - 11:24 AM
Following on from this post, I thought it'd be fun to see how my own packager fared with the JVML folder. It didn't do very well, of course (something like 260kb once uploaded - you've got decent-sized pre-compressed jar in there which throws a bit of a spanner into the works), but in order to get a copy of JVML I had to go through Grin.

The thing that surprised me was how slow Grin was. I realise a lot of that has to do with the rather more processor-intensive operations involved in unpacking zips, but I decided to dig around in case there was anything obvious you could do to improve matters.

I noticed that you're using os.time() to track when you should yield - that function should be avoided unless you actually want to know the MineCraft time. Your script will fail if the user happens to run it just before midnight, because os.time() will suddenly go from ~23.something to ~0.something, and Grin will hence decide that it doesn't need to yield for the next day. Use os.clock() instead - that'll keep increasing until the system reboots. You may even consider simply yielding whenever a certain part of your loop structures are passed over, without bothering to check the time at all.

Speaking of which, when you yield, consider avoiding sleep(0). This wastes a tick, as timers take a minimum of a twentieth of a second to fire their events. Queue a custom event instead (eg tostring({})) then pull that.
MKlegoman357 #12
Posted 11 April 2015 - 02:26 PM
Speaking of which, when you yield, consider avoiding sleep(0). This wastes a tick, as timers take a minimum of a twentieth of a second to fire their events. Queue a custom event instead (eg tostring({})) then pull that.

If you only need to yield, then creating a table, tostring'ing it and queuing it, then pulling an event with a filter can be relatively slow. Just queue an empty string and pull any event, works faster than anything else.


os.queueEvent("") os.pullEvent()
Edited on 11 April 2015 - 12:26 PM
ElvishJerricco #13
Posted 11 April 2015 - 06:08 PM
- snip -

Thanks for the tip!
Bomb Bloke #14
Posted 12 April 2015 - 12:43 AM
If you only need to yield, then creating a table, tostring'ing it and queuing it, then pulling an event with a filter can be relatively slow.

Good point. I've kinda gotten into the habit of trying to flush the whole queue out, but there's really no "need" for that.
ElvishJerricco #15
Posted 12 April 2015 - 01:14 AM
Bomb Bloke said:

There'd be a lot more that I could do to speed up grin if CC supported file seeking. As it stands, the big reason it takes so long is because it loads the entire zip file into memory in one go, than traverses back and forth over that one huge table. But without file seeking, there isn't really another way.
Bomb Bloke #16
Posted 12 April 2015 - 01:29 AM
Eh what? Seeking through a table loaded in RAM is always going to be faster than seeking through a file. :blink:/>
ElvishJerricco #17
Posted 12 April 2015 - 02:32 AM
Eh what? Seeking through a table loaded in RAM is always going to be faster than seeking through a file. :blink:/>

But there's extra steps added by having to scan the whole file once before getting to actually use it. Rather than finding the end of the zip file for the directory table, finding files, and seeking to them to read them directly, I have to load the entire thing once, reading one time, then I have scan around for the directory, then I can read from memory. It's slower.
CrazedProgrammer #18
Posted 20 June 2017 - 04:16 PM
Hi ElvishJerrico,

I'm having issues getting grin-get installed.
It seems to have to do with the JSON library not downloading correctly:

Any idea how I could fix this?
SquidDev #19
Posted 20 June 2017 - 04:17 PM
Hi ElvishJerrico,

I'm having issues getting grin-get installed.
It seems to have to do with the JSON library not downloading correctly:
This is fixed on the latest version, which hasn't been released yet. I'll try to get round to that later today. Sorry.