Skip to content

Commit

Permalink
Revert "Revert "Library Publishing MVP (#1898)"" (#1933)
Browse files Browse the repository at this point in the history
  • Loading branch information
radeusgd authored Aug 9, 2021
1 parent 3b7d6e1 commit 31167fd
Show file tree
Hide file tree
Showing 39 changed files with 1,497 additions and 158 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/scala.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ env:
# Please ensure that this is in sync with rustVersion in build.sbt
rustToolchain: nightly-2021-05-12
# Some moderately recent version of Node.JS is needed to run the library download tests.
nodeVersion: 12.18.4
nodeVersion: 14.17.2

jobs:
test_and_publish:
Expand Down
7 changes: 7 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Enso Next

## Tooling

- Implemented a basic library uploader
([#1933](https://github.com/enso-org/enso/pull/1933)). It implements the
`library/publish` endpoint of the Language Server and adds a `publish-library`
subcommand to the Launcher.

# Enso 0.2.23 (2021-08-09)

## Miscellaneous
Expand Down
48 changes: 32 additions & 16 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ lazy val enso = (project in file("."))
`distribution-manager`,
`edition-updater`,
`library-manager`,
`library-manager-test`,
syntax.jvm,
testkit
)
Expand Down Expand Up @@ -1023,6 +1024,7 @@ lazy val `language-server` = (project in file("engine/language-server"))
.dependsOn(`version-output`)
.dependsOn(pkg)
.dependsOn(testkit % Test)
.dependsOn(`library-manager-test` % Test)
.dependsOn(`runtime-version-manager-test` % Test)

lazy val ast = (project in file("lib/scala/ast"))
Expand Down Expand Up @@ -1178,22 +1180,6 @@ lazy val runtime = (project in file("engine/runtime"))
case _ => MergeStrategy.first
}
)
.settings(
(Compile / compile) := (Compile / compile)
.dependsOn(
Def.task {
Editions.writeEditionConfig(
ensoVersion = ensoVersion,
editionName = currentEdition,
libraryVersion =
"0.1.0", // TODO [RW] Once we start releasing the standard libraries, this will be synced with engine version.
log = streams.value.log
)
}
)
.value,
cleanFiles += baseDirectory.value / ".." / ".." / "distribution" / "editions"
)
.dependsOn(pkg)
.dependsOn(`interpreter-dsl`)
.dependsOn(syntax.jvm)
Expand Down Expand Up @@ -1270,6 +1256,7 @@ lazy val `engine-runner` = project
)
.dependsOn(`version-output`)
.dependsOn(pkg)
.dependsOn(cli)
.dependsOn(`library-manager`)
.dependsOn(`language-server`)
.dependsOn(`polyglot-api`)
Expand Down Expand Up @@ -1359,6 +1346,22 @@ lazy val editions = project
"org.scalatest" %% "scalatest" % scalatestVersion % Test
)
)
.settings(
(Compile / compile) := (Compile / compile)
.dependsOn(
Def.task {
Editions.writeEditionConfig(
ensoVersion = ensoVersion,
editionName = currentEdition,
libraryVersion =
"0.1.0", // TODO [RW] Once we start releasing the standard libraries, this will be synced with engine version.
log = streams.value.log
)
}
)
.value,
cleanFiles += baseDirectory.value / ".." / ".." / "distribution" / "editions"
)
.dependsOn(testkit % Test)

lazy val downloader = (project in file("lib/scala/downloader"))
Expand Down Expand Up @@ -1406,6 +1409,19 @@ lazy val `library-manager` = project
.dependsOn(testkit % Test)
.dependsOn(`logging-service` % Test)

lazy val `library-manager-test` = project
.in(file("lib/scala/library-manager-test"))
.configs(Test)
.settings(
libraryDependencies ++= Seq(
"com.typesafe.scala-logging" %% "scala-logging" % scalaLoggingVersion,
"org.scalatest" %% "scalatest" % scalatestVersion % Test
)
)
.dependsOn(`library-manager`)
.dependsOn(testkit)
.dependsOn(`logging-service`)

lazy val `runtime-version-manager` = project
.in(file("lib/scala/runtime-version-manager"))
.configs(Test)
Expand Down
27 changes: 27 additions & 0 deletions docs/language-server/protocol-language-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ transport formats, please look [here](./protocol-architecture).
- [`LibraryDownloadError`](#librarydownloaderror)
- [`LocalLibraryNotFound`](#locallibrarynotfound)
- [`LibraryNotResolved`](#librarynotresolved)
- [`InvalidLibraryName`](#invalidlibraryname)

<!-- /MarkdownTOC -->

Expand Down Expand Up @@ -4344,6 +4345,8 @@ null;

#### Errors

- [`InvalidLibraryName`](#invalidlibraryname) to signal that the selected
library name is not valid.
- [`LibraryAlreadyExists`](#libraryalreadyexists) to signal that a library with
the given namespace and name already exists.
- [`FileSystemError`](#filesystemerror) to signal a generic, unrecoverable
Expand Down Expand Up @@ -4422,6 +4425,9 @@ versions. This is a temporary solution and in the longer-term it should be
replaced with separate settings allowing to arbitrarily modify the library
version from the IDE.

The `uploadUrl` is the URL of the library repository that accepts library
uploads.

The metadata for publishing the library can be set with
[`library/setMetadata`](#librarysetmetadata). If it was not set, the publish
operation will still proceed, but that metadata will be missing.
Expand All @@ -4433,6 +4439,7 @@ operation will still proceed, but that metadata will be missing.
namespace: String;
name: String;
authToken: String;
uploadUrl: String;

bumpVersionAfterPublish?: Boolean;
}
Expand All @@ -4446,6 +4453,8 @@ null;

#### Errors

- [`LocalLibraryNotFound`](#locallibrarynotfound) to signal that a local library
with the given name does not exist on the local libraries path.
- [`LibraryPublishError`](#librarypublisherror) to signal that the server did
not accept to publish the library (for example because a library with the same
version already exists).
Expand Down Expand Up @@ -5010,3 +5019,21 @@ there either.
}
}
```

### `InvalidLibraryName`

Signals that the chosen library name is invalid.

It contains a suggestion of a similar name that is valid.

For example for `FooBar` it will suggest `Foo_Bar`.

```typescript
"error" : {
"code" : 8009,
"message" : "[<name>] is not a valid name: <reason>.",
"payload" : {
"suggestedName" : "<fixed-name>"
}
}
```
25 changes: 25 additions & 0 deletions docs/libraries/repositories.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,31 @@ them), it will result in the following merged directory structure:
└── LICENSE.md
```

### Publishing

To be able to publish libraries to a repository, the repository must provide an
upload endpoint which satisfies the following requirements.

The endpoint should get the library name and version from the query parameters:
`namespace`, `name` and `version`.

It should check any authentication data attached to the query and verify that
the user has sufficient privileges to upload the library for that `namespace`.

Currently, we use a static check which checks an `Auth-Token` header for a
pre-determined secret key, but any other authentication schemes can be used, as
long as they are supported by the GUI or CLI.

Then, the server must check if a library with the given name and version
combination already exists. If the library already exists, the request should be
rejected with `409 Conflict` status code indicating that a conflicting library
is already in the repository.

If the request goes through, the server should create a directory for the
library and put any files attached to the request there. Each request should
always contain `package.yaml` and `manifest.yaml` files attached and at least
one sub-archive, usually called `main.tgz`.

## Editions Repository

The Editions repository has a very simple structure.
Expand Down
16 changes: 14 additions & 2 deletions docs/libraries/sharing.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,17 @@ import <namespace>.<Project_Name>

## Publishing

> Soon it will be possible to share the libraries through the Marketplace, but
> it is still a work in progress.
To publish a library, first you must obtain the upload URL of the repository, if
you are hosting the repository locally it will be `http://localhost:8080/upload`
(or possibly with a different port if that was overridden).

If the repository requires authentication, it is best to set it up by setting
the `ENSO_AUTH_TOKEN` environment variable to the value of your secret token.

Then you can use the Enso CLI to upload the project:

```bash
enso publish-library --upload-url <URL> <path to project root>
```

See `enso publish-library --help` for more information.
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ object LibraryApi {
namespace: String,
name: String,
authToken: String,
uploadUrl: String,
bumpVersionAfterPublish: Option[Boolean]
)

Expand Down Expand Up @@ -232,4 +233,16 @@ object LibraryApi {
} """
)
}

case class InvalidLibraryName(
originalName: String,
suggestedName: String,
reason: String
) extends Error(8009, s"[$originalName] is not a valid name: $reason.") {
override def payload: Option[Json] = Some(
json""" {
"suggestedName" : $suggestedName
} """
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ import com.typesafe.scalalogging.LazyLogging
import org.enso.distribution.{DistributionManager, FileSystem}
import org.enso.editions.{Editions, LibraryName}
import org.enso.languageserver.libraries.LocalLibraryManagerProtocol._
import org.enso.librarymanager.local.LocalLibraryProvider
import org.enso.librarymanager.local.{
DefaultLocalLibraryProvider,
LocalLibraryProvider
}
import org.enso.pkg.PackageManager
import org.enso.pkg.validation.NameValidation

import java.io.File
import java.nio.file.Files
Expand Down Expand Up @@ -34,9 +38,18 @@ class LocalLibraryManager(
sender() ! listLocalLibraries()
case Create(libraryName, authors, maintainers, license) =>
sender() ! createLibrary(libraryName, authors, maintainers, license)
case Publish(_, _, _) =>
logger.error("Publishing libraries is currently not implemented.")
sender() ! Failure(new NotImplementedError())
case FindLibrary(libraryName) =>
sender() ! findLibrary(libraryName)
}
}

/** Checks if the library name is a valid Enso module name. */
private def validateLibraryName(libraryName: LibraryName): Unit = {
// TODO [RW] more specific exceptions
NameValidation.validateName(libraryName.name) match {
case Left(error) =>
throw new RuntimeException(s"Library name is not valid: [$error].")
case Right(_) =>
}
}

Expand All @@ -54,6 +67,8 @@ class LocalLibraryManager(
// TODO [RW] modify protocol to be able to create Contact instances
val _ = (authors, maintainers)

validateLibraryName(libraryName)

// TODO [RW] make the exceptions more relevant
val possibleRoots = LazyList
.from(distributionManager.paths.localLibrariesSearchPaths)
Expand Down Expand Up @@ -104,6 +119,17 @@ class LocalLibraryManager(
} yield LibraryName(namespace, name)
}

/** Finds the path on the filesystem to a local library. */
private def findLibrary(
libraryName: LibraryName
): Try[FindLibraryResponse] = Try {
val localLibraryProvider = new DefaultLocalLibraryProvider(
distributionManager.paths.localLibrariesSearchPaths.toList
)
val pathOpt = localLibraryProvider.findLibrary(libraryName)
FindLibraryResponse(pathOpt)
}

/** Finds the edition associated with the current project, if specified in its
* config.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package org.enso.languageserver.libraries

import org.enso.editions.LibraryName

import java.nio.file.Path

object LocalLibraryManagerProtocol {

/** A top class representing any request to the [[LocalLibraryManager]]. */
Expand Down Expand Up @@ -37,10 +39,9 @@ object LocalLibraryManagerProtocol {
license: String
) extends Request

/** A request to publish a library. */
case class Publish(
libraryName: LibraryName,
authToken: String,
bumpVersionAfterPublish: Boolean
) extends Request
/** A request to find the path to a local library. */
case class FindLibrary(libraryName: LibraryName) extends Request

/** A response to [[FindLibrary]]. */
case class FindLibraryResponse(libraryRoot: Option[Path])
}
Loading

0 comments on commit 31167fd

Please sign in to comment.