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

Expand selections file handling to handle new versions #2803

Merged
merged 1 commit into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions source/dub/project.d
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ class Project {
*/
static package SelectedVersions loadSelections(in Package pack)
{
import dub.version_;
import dub.internal.dyaml.stdsumtype;

auto selverfile = (pack.path ~ SelectedVersions.defaultFile).toNativeString();

// No file exists
Expand All @@ -113,13 +116,23 @@ class Project {
// TODO: Remove `StrictMode.Warn` after v1.40 release
// The default is to error, but as the previous parser wasn't
// complaining, we should first warn the user.
auto selected = parseConfigFileSimple!Selected(selverfile, StrictMode.Warn);
auto selected = parseConfigFileSimple!SelectionsFile(selverfile, StrictMode.Warn);

// Parsing error, it will be displayed to the user
if (selected.isNull())
return new SelectedVersions();

return new SelectedVersions(selected.get());
return selected.get().content.match!(
(Selections!0 s) {
logWarnTag("Unsupported version",
"File %s has fileVersion %s, which is not yet supported by DUB %s.",
selverfile, s.fileVersion, dubVersion);
logWarn("Ignoring selections file. Use a newer DUB version " ~
"and set the appropriate toolchainRequirements in your recipe file");
return new SelectedVersions();
},
(Selections!1 s) => new SelectedVersions(s),
);
}

/** List of all resolved dependencies.
Expand Down Expand Up @@ -1757,7 +1770,7 @@ unittest
public class SelectedVersions {
protected {
enum FileVersion = 1;
Selected m_selections;
Selections!1 m_selections;
bool m_dirty = false; // has changes since last save
bool m_bare = true;
}
Expand All @@ -1766,13 +1779,14 @@ public class SelectedVersions {
enum defaultFile = "dub.selections.json";

/// Constructs a new empty version selection.
public this(uint version_ = FileVersion) @safe pure nothrow @nogc
public this(uint version_ = FileVersion) @safe pure
{
this.m_selections = Selected(version_);
enforce(version_ == 1, "Unsupported file version");
this.m_selections = Selections!1(version_);
}

/// Constructs a new non-empty version selection.
public this(Selected data) @safe pure nothrow @nogc
public this(Selections!1 data) @safe pure nothrow @nogc
{
this.m_selections = data;
this.m_bare = false;
Expand Down
118 changes: 110 additions & 8 deletions source/dub/recipe/selection.d
Original file line number Diff line number Diff line change
@@ -1,22 +1,110 @@
/**
* Contains type definition for `dub.selections.json`
* Contains type definition for the selections file
*
* The selections file, commonly known by its file name
* `dub.selections.json`, is used by Dub to store resolved
* dependencies. Its purpose is identical to other package
* managers' lock file.
*/
module dub.recipe.selection;

import dub.dependency;
import dub.internal.vibecompat.inet.path : NativePath;

import dub.internal.configy.Attributes;
import dub.internal.dyaml.stdsumtype;

import std.exception;

public struct Selected
deprecated("Use either `Selections!1` or `SelectionsFile` instead")
public alias Selected = Selections!1;

/**
* Top level type for `dub.selections.json`
*
* To support multiple version, we expose a `SumType` which
* contains the "real" version being parsed.
*/
public struct SelectionsFile
{
/// Private alias to avoid repetition
private alias DataType = SumType!(Selections!0, Selections!1);

/**
* Get the `fileVersion` of this selection file
*
* The `fileVersion` is always present, no matter the version.
* This is a convenience function that matches any version and allows
* one to retrieve it.
*
* Note that the `fileVersion` can be an unsupported version.
*/
public uint fileVersion () const @safe pure nothrow @nogc
{
return this.content.match!((s) => s.fileVersion);
}

/**
* The content of this selections file
*
* The underlying content can be accessed using
* `dub.internal.yaml.stdsumtype : match`, for example:
* ---
* SelectionsFile file = readSelectionsFile();
* file.content.match!(
* (Selections!0 s) => logWarn("Unsupported version: %s", s.fileVersion),
* (Selections!1 s) => logWarn("Old version (1), please upgrade!"),
* (Selections!2 s) => logInfo("You are up to date"),
* );
* ---
*/
public DataType content;

/**
* Deserialize the selections file according to its version
*
* This will first deserialize the `fileVersion` only, and then
* the expected version if it is supported. Unsupported versions
* will be returned inside a `Selections!0` struct,
* which only contains a `fileVersion`.
*/
public static SelectionsFile fromYAML (scope ConfigParser!SelectionsFile parser)
{
import dub.internal.configy.Read;

static struct OnlyVersion { uint fileVersion; }

auto vers = parseConfig!OnlyVersion(
CLIArgs.init, parser.node, StrictMode.Ignore);

switch (vers.fileVersion) {
case 1:
return SelectionsFile(DataType(parser.parseAs!(Selections!1)));
default:
return SelectionsFile(DataType(Selections!0(vers.fileVersion)));
}
}
}

/**
* A specific version of the selections file
*
* Currently, only two instantiations of this struct are possible:
* - `Selections!0` is an invalid/unsupported version;
* - `Selections!1` is the most widespread version;
*/
public struct Selections (ushort Version)
{
/// The current version of the file format
public uint fileVersion;
///
public uint fileVersion = Version;

/// The selected package and their matching versions
public SelectedDependency[string] versions;
static if (Version == 0) { /* Invalid version */ }
else static if (Version == 1) {
/// The selected package and their matching versions
public SelectedDependency[string] versions;
}
else
static assert(false, "This version is not supported");
}


Expand Down Expand Up @@ -97,12 +185,26 @@ unittest
}
}`;

auto s = parseConfigString!Selected(content, "/dev/null");
assert(s.fileVersion == 1);
auto file = parseConfigString!SelectionsFile(content, "/dev/null");
assert(file.fileVersion == 1);
auto s = file.content.match!(
(Selections!1 s) => s,
(s) { assert(0); return Selections!(1).init; },
);
assert(s.versions.length == 5);
assert(s.versions["simple"] == Dependency(Version("1.5.6")));
assert(s.versions["branch"] == Dependency(Version("~master")));
assert(s.versions["branch2"] == Dependency(Version("~main")));
assert(s.versions["path"] == Dependency(NativePath("../some/where")));
assert(s.versions["repository"] == Dependency(Repository("git+https://github.com/dlang/dub", "123456123456123456")));
}

// Test reading an unsupported version
unittest
{
import dub.internal.configy.Read : parseConfigString;

immutable string content = `{"fileVersion": 9999, "thisis": "notrecognized"}`;
auto s = parseConfigString!SelectionsFile(content, "/dev/null");
assert(s.fileVersion == 9999);
}
4 changes: 2 additions & 2 deletions source/dub/test/base.d
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,13 @@ public class TestSelectedVersions : SelectedVersions {
import dub.recipe.selection;

/// Forward to parent's constructor
public this(uint version_ = FileVersion) @safe pure nothrow @nogc
public this(uint version_ = FileVersion) @safe pure
{
super(version_);
}

/// Ditto
public this(Selected data) @safe pure nothrow @nogc
public this(Selections!1 data) @safe pure nothrow @nogc
{
super(data);
}
Expand Down
Loading