//===----------------------------------------------------------------------===// // Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //===----------------------------------------------------------------------===// /// A manifest that defines the presence of a project. /// /// A project is useful for defining [dependencies], and also for defining common evaluator /// [settings]. /// /// A project is a directory that contains a project file at its root. /// The project file is a file named `PklProject` exactly, that amends or embeds `"pkl:Project"`. /// /// When evaluating from the CLI, the project directory can be specified explicitly using /// `--project-dir` flag, or is determined implicitly by walking up the working directory, looking /// for a directory containing a `PklProject` file. /// /// When using the API libraries (e.g. _pkl-swift_, _pkl-go_ or _pkl-config-java_), the project must be /// explicitly stated via the evaluator builder. /// /// ## Embedding [Project] /// /// In a project file, instead of amending `pkl:Project`, it is possible simply set the /// `output.value` property of a module to an instance of [Project]. /// This allows defining higher levels of abstraction that encapsulate an underlying [Project] /// definition. /// /// When defining an abstraction, it is important to: /// 1. Set the module's `output.value` to the underlying project output. /// 2. Set [projectFileUri] to the enclosing module's URI. /// This is necessary to determine the correct project directory, as well as for resolving /// local project dependencies correctly. /// /// The [newInstance()] helper method exists as a convenient way to set this when embedding /// project definitions. /// /// Example: /// ``` /// module MyTeamProject /// /// import "pkl:Project" /// /// /// The package name, to be prefixed with `myteam`. /// packageName: String /// /// project: Project = (Project.newInstance(module)) { /// package { /// name = "myteam.\(packageName)" /// } /// } /// /// output { /// value = project /// } /// ``` @ModuleInfo { minPklVersion = "0.31.0" } module pkl.Project import "pkl:EvaluatorSettings" as EvaluatorSettingsModule import "pkl:Project" import "pkl:reflect" import "pkl:semver" /// The details for the package represented by this project. /// /// This section is used if publishing this project as a package. package: Package? /// The tests of the project. /// /// If set, allows running `pkl test` without specifying the paths to source modules. /// /// Relative paths are resolved against PklProject's enclosing directory. /// /// Glob imports can be useful for defining this property. /// /// Example: /// /// ``` /// tests = import*("**.test.pkl").keys.toListing() /// ``` tests: Listing(isDistinct) /// Tells if the project is a local module named `PklProject`, is not self, and has a [package] section local isValidLoadDependency = (it: Project) -> isUriLocal(projectFileUri, it.projectFileUri) && it.projectFileUri.endsWith("/PklProject") && it != module && it.package != null local const function isUriLocal(uri1: Uri, uri2: Uri): Boolean = // This is an imperfect check; should also check that the URIs have the same authority. // We should improve this if/when there is a URI library in the stdlib. uri1.substring(0, uri1.indexOf(":")) == uri2.substring(0, uri2.indexOf(":")) /// The dependencies of this project. /// /// A dependency is a group of Pkl modules and file resources that can be imported within the /// project. /// Within the project, a dependency can be referenced via _dependency notation_, where the name /// is prefixed with the `@` character. /// /// For example, given the following descriptor: /// /// ``` /// dependencies { /// ["birds"] { /// uri = "package://example.com/birds@1.0.0" /// } /// } /// ``` /// /// This enables the following snippet: /// /// ``` /// import "@birds/canary.pkl" // Import a module from the `birds` dependency /// /// birdIndex = read("@birds/index.txt") // Read a file from the `birds` dependency /// ``` /// /// A dependency's coordinates can either be specified in the form of a [RemoteDependency] /// descriptor, which gets fetched over the network, or an import of another PklProject file, which /// represents a package that exists locally. /// /// Remote dependencies are fetched over HTTPS. /// When fetching a remote dependency, two HTTPS requests are made. /// Given dependency URI `package://example.com/foo@0.5.0`, the following requests are made: /// /// 1. `GET https://example.com/foo@0.5.0` to retrieve the metadata JSON file. /// 2. Given the metadata JSON file, make a GET request to the package URI ZIP archive. /// /// If this project is published as a package, these dependencies are included within the published /// package metadata. /// /// ## Local project dependencies /// /// A local project can alternatively be used as a dependency. /// This is useful when structuring a single repository that publishes multiple packages. /// /// To specify a local project dependency, import the relative `PklProject` file. /// /// The local project dependency must define its own [package]. /// /// Example: /// ``` /// dependencies { /// ["penguins"] = import("../penguins/PklProject") /// } /// ``` /// /// ## Resolving dependencies /// /// Dependencies must be _resolved_ before they can be used. /// To resolve dependencies, run the CLI command `pkl project resolve`. /// This will generate a `PklProject.deps.json` file next to the `PklProject` file. /// /// ### Minimum version selection /// /// Pkl uses the /// [minimum version selection algorithm 1](https://research.swtch.com/vgo-mvs#algorithm_1) /// to resolve dependencies. /// A dependency is identified by its package URI, as well as its major semver number. /// /// To determine the resolved dependencies of a project, the following algorithm is applied: /// 1. Gather all dependencies, both direct and transitive. /// 2. For each package's major version, determine the highest declared minor version. /// 3. Write each resolved dependency to sibling file `PklProject.deps.json`. dependencies: Mapping local isFileBasedProject = projectFileUri.startsWith("file:") /// If set, controls the base evaluator settings when running the evaluator. /// /// These settings influence the behavior of the evaluator when running the `pkl eval`, `pkl test`, /// and `pkl repl` CLI commands. /// Command line flags passed to the CLI will override any settings defined here. /// /// Other integrations can possibly ignore these evaluator settings. /// /// Evaluator settings do not get published as part of a package. /// It is not possible for a package dependency to influence the evaluator settings of a project. /// /// The following values can only be set if this is a file-based project. /// /// - [modulePath][EvaluatorSettings.modulePath] /// - [rootDir][EvaluatorSettings.rootDir] /// - [moduleCacheDir][EvaluatorSettings.moduleCacheDir] /// /// For each of these, relative paths are resolved against the project's enclosing directory. evaluatorSettings: EvaluatorSettingsModule( (modulePath != null).implies(isFileBasedProject), (rootDir != null).implies(isFileBasedProject), (moduleCacheDir != null).implies(isFileBasedProject), ) /// The URI of the PklProject file. /// /// This value is used to resolve relative paths when importing another local project as a /// dependency. projectFileUri: String = reflect.Module(module).uri /// Instantiates a project definition within the enclosing module. /// /// This is a convenience method for setting [projectFileUri] to the enclosing module's URI. /// /// Example: /// ``` /// myProject: Project = (Project.newInstance(module)) { /// dependencies { /* etc */ } /// } /// ``` function newInstance(enclosingModule: Module): Project = new { projectFileUri = reflect.Module(enclosingModule).uri } local const hasVersion = (it: Uri) -> let (versionSep = it.lastIndexOf("@")) if (versionSep == -1) false else let (version = it.drop(versionSep + 1)) semver.parseOrNull(version) != null typealias PackageUri = Uri(startsWith("package:"), hasVersion) class RemoteDependency { /// The URI that this dependency is published to. uri: PackageUri /// The checksums of this package. /// /// If omitted, this is taken from the derived package coordinates. checksums: Checksums? } class Checksums { /// The [SHA-256](https://en.wikipedia.org/wiki/SHA-2) checksum value of the dependency, in hexadecimal representation. sha256: String } /// An email address, conformant to the /// [RFC5322 mailbox](https://www.rfc-editor.org/rfc/rfc5322#section-3.4) specification. /// /// Can be in the form of an address spec, or a named address. /// /// Examples: /// * `"johnny.appleseed@example.com"` /// * `"Johnny Appleseed "` typealias EmailAddress = String(matches(Regex(#".+@\S+|.+<\S+@\S+>"#))) class Package { /// The name of this package. /// /// The package name is only used for display purposes. /// /// Example: /// ``` /// name = "myproject" /// ``` name: String /// The URI that the package is published to, without the version part. /// /// This, along with the version, determines the import path for modules and resources published /// by this package. /// /// Example: /// ``` /// baseUri = "package://example.com/myproject" /// ``` baseUri: Uri(startsWith("package:")) /// The version of this package. /// /// Must adhere to semantic versioning. /// /// Example: /// ``` /// version = "1.5.0" /// ``` version: String(semver.isValid(this)) /// The HTTPS location for the zip archive for this package. /// /// Example: /// ``` /// packageZipUrl = "https://example.com/artifacts/myproject/\(version).zip" /// ``` packageZipUrl: Uri(startsWith("https:")) /// The description of this package. description: String? /// The maintainers' emails for this package. /// /// Email addresses must adhere to /// [RFC5322 mailbox](https://www.rfc-editor.org/rfc/rfc5322#section-3.4) specification. /// /// Example: /// ``` /// email { "Johnny Appleseed " } /// ``` authors: Listing /// The website for this package. /// /// Example: /// ``` /// website = "https://example.com/myproject" /// ``` website: String? /// The web URL of the Pkldoc documentation for this package. documentation: Uri(!endsWith("/"))? /// The source code repository for this package. /// /// Example: /// ``` /// sourceCode = "https://github.com/myorg/myproject" /// ``` sourceCode: String? /// The source code scheme for this package. /// /// This is used to transform stack frames for errors arising for this package. /// /// The following placeholders are available: /// /// - `%{path}` /// absolute file path of the file to open /// - `%{line}` /// start line number to navigate to /// - `%{endLine}` /// end line number to navigate to /// - `%{column}` /// column number to navigate to /// - `%{endColumn}` /// end column number to navigate to /// /// For example, if publishing to GitHub, assuming that the version gets published as a tag: /// /// ``` /// sourceCodeUrlScheme = "\(sourceCode)/blob/\(version)%{path}#L%{line}-L%{endLine}" /// ``` sourceCodeUrlScheme: String? /// The license associated with this package. /// /// If using a common license, use its [SPDX license identifier](https://spdx.org/licenses/). /// /// If using multiple common licenses, use a /// [SPDX license expression syntax version 2.0 string](https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/). /// For example: `"Apache-2.0 or MIT"`. /// /// If using an uncommon license, also provide its full text in the [licenseText] property. /// /// Example: /// ``` /// license = "Apache-2.0" /// ``` license: (CommonSpdxLicenseIdentifier | String)? /// The full text of the license associated with this package. licenseText: String? /// The web URL of the issue tracker for this package. issueTracker: String? /// Paths to the tests that define the API of the package. /// /// These tests are run as part of the `pkl project package` command. /// /// Relative paths are resolved against PklProject's enclosing directory. /// /// Glob imports can be useful for defining this property. /// /// Example: /// /// ``` /// tests = import*("**.test.pkl").keys.toListing() /// ``` apiTests: Listing(isDistinct) /// Glob patterns describing the set of files to exclude from packaging. /// /// By default, the project manifest files are excluded, and any paths that start with a dot. /// /// Glob patterns follows the same glob rules as glob imports and reads. exclude: Listing = new { "PklProject" "PklProject.deps.json" ".**" } /// The effective package URI for the package represented by this project. fixed uri: PackageUri = "\(baseUri)@\(version)" } @Deprecated { since = "0.26.0"; replaceWith = "EvaluatorSettingsModule" } typealias EvaluatorSettings = EvaluatorSettingsModule /// Common software licenses in the [SPDX License List](https://spdx.org/licenses/). typealias CommonSpdxLicenseIdentifier = "Apache-2.0" | "MIT" | "BSD-2-Clause" | "BSD-3-Clause" | "ISC" | "GPL-3.0" | "GPL-2.0" | "MPL-2.0" | "MPL-1.1" | "MPL-1.0" | "AGPL-1.0-only" | "AGPL-1.0-or-later" | "AGPL-3.0-only" | "AGPL-3.0-or-later" | "LGPL-2.0-only" | "LGPL-2.0-or-later" | "LGPL-2.1-only" | "LGPL-2.1-or-later" | "LGPL-3.0-only" | "LGPL-3.0-or-later" | "EPL-1.0" | "EPL-2.0" | "UPL-1.0" | "BSL-1.0" | "Unlicense" @Unlisted @Since { version = "0.27.0" } fixed annotations: List = reflect.moduleOf(this).annotations