Skip to content

Commit

Permalink
Remember capitalisation of library when installed
Browse files Browse the repository at this point in the history
Addresses #529

This way, list shows the correct version of the name, and path falls
back to the correct version when no haxelib.json is found.

The "correct" version is the way the name in haxelib.json is
capitalized, or if no such file exists it is the way the parameter to
the `dev`/`git`/`hg` command was capitalized. Additionally, if
the parameter is an alias, then it is used instead as well.

It is updated everytime a version is set as current. However, officially
released library version names are preferred over the capitalization of
`dev`, `git`, or `hg` versions.

For example, after:
`haxelib dev LIBRARY <path>`
`haxelib install library 1.0.0`

If `library` is the version in `haxelib.json`, then that is preferred
over `LIBRARY` after we install from the server.

Throw error in `path` command when two versions of the same lib are
queried even if the two versions are capitalized differently.
- e.g. `LiBrArY:1.2.0` and `library:1.3.0`

Ensure that haxelib run sets HAXELIB_RUN_NAME to name field in
`haxelib.json`.

Ignore capitalized directories in `list`.
  • Loading branch information
tobil4sk authored and Simn committed Apr 6, 2022
1 parent 60ca162 commit 92ee0f4
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 32 deletions.
27 changes: 23 additions & 4 deletions src/haxelib/Data.hx
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,18 @@ abstract ProjectName(String) to String {
case v: v;
}

/**
If `alias` is just a different capitalization of `correct`, returns `correct`.
If `alias` is completely different, returns `alias` instead.
**/
static public function getCorrectOrAlias(correct:ProjectName, alias:ProjectName) {
return if (correct.toLowerCase() == alias.toLowerCase())
correct
else
alias;
}

/** Default project name **/
static public var DEFAULT(default, null) = new ProjectName('unknown');
}
Expand Down Expand Up @@ -420,15 +432,22 @@ class Data {
}
}

/** Extracts project information from `jsondata`, validating it according to `check`. **/
public static function readData( jsondata: String, check : CheckLevel ) : Infos {
/**
Extracts project information from `jsondata`, validating it according to `check`.
`defaultName` is the project name to use if it is empty when the check value allows it.
**/
public static function readData( jsondata: String, check : CheckLevel, ?defaultName:ProjectName ) : Infos {
if (defaultName == null)
defaultName = ProjectName.DEFAULT;

var doc:Infos =
try Json.parse(jsondata)
catch ( e : Dynamic )
if (check >= CheckLevel.CheckSyntax)
throw 'JSON parse error: $e';
else {
name : ProjectName.DEFAULT,
name : defaultName,
url : '',
version : SemVer.DEFAULT,
releasenote: 'No haxelib.json found',
Expand Down Expand Up @@ -457,7 +476,7 @@ class Data {
doc.classPath = '';

if (doc.name.validate() != None)
doc.name = ProjectName.DEFAULT;
doc.name = defaultName;

if (doc.description == null)
doc.description = '';
Expand Down
15 changes: 10 additions & 5 deletions src/haxelib/api/GlobalScope.hx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ class GlobalScope extends Scope {
Dependency.fromNameAndVersion(name, version)];

final libraryRunData = {
name: library,
name: ProjectName.getCorrectOrAlias(info.name, library),
internalName: info.name,
version: resolved.version,
dependencies: dependencies,
path: resolved.path,
Expand Down Expand Up @@ -139,15 +140,17 @@ class GlobalScope extends Scope {

while (!stack.isEmpty()) {
final cur = stack.pop();
final library = cur.library;
// turn it to lowercase always (so that `LiBrArY:1.2.0` and `library:1.3.0` clash because
// they are different versions), and then get the correct name if provided
final library = repository.getCorrectName(ProjectName.ofString(cur.library.toLowerCase()));
final version = cur.version;

// check for duplicates
if (includedLibraries.exists(library)) {
final otherVersion = includedLibraries[library];
// if the current library is part of the original set of inputs, and if the versions don't match
if (topLevelLibs.contains(cur) && version != null && version != otherVersion)
throw 'Cannot process `$library:$version`:'
throw 'Cannot process `${cur.library}:$version`: '
+ 'Library $library has two versions included : $otherVersion and $version';
continue;
}
Expand All @@ -167,7 +170,7 @@ class GlobalScope extends Scope {

final info = {
final jsonContent = try File.getContent(resolved.path + Data.JSON) catch (_) null;
Data.readData(jsonContent, jsonContent != null ? CheckSyntax : NoCheck);
Data.readData(jsonContent, jsonContent != null ? CheckSyntax : NoCheck, library);
}

// path and version compiler define
Expand All @@ -177,7 +180,9 @@ class GlobalScope extends Scope {
else
resolved.path
);
addLine('-D $library=${info.version}');
// if info.name is not the placeholder for an empty name (i.e. unknown), use it, otherwise
// fall back to the value in the .name file
addLine('-D ${info.name}=${info.version}');

// add dependencies to stack
final dependencies = info.dependencies.toArray();
Expand Down
50 changes: 37 additions & 13 deletions src/haxelib/api/Installer.hx
Original file line number Diff line number Diff line change
Expand Up @@ -275,20 +275,24 @@ class Installer {
continue;
}

if (library.isLatest || !scope.isLibraryInstalled(library.name))
setVersionAndLog(library.name, library.installData);
final libraryName = switch library.installData {
case VcsInstall(version, vcsData): getName(library.name, version, vcsData.subDir);
case _: library.name;
}

if (library.isLatest || !scope.isLibraryInstalled(libraryName))
setVersionAndLog(libraryName, library.installData);

userInterface.log("Done");

handleDependenciesGeneral(library.name, library.installData);
handleDependenciesGeneral(libraryName, library.installData);
}
}

/** Installs the latest version of `library` from the haxelib server. **/
public function installLatestFromHaxelib(library:ProjectName) {
final info = Connection.getInfo(library);
// TODO with stricter capitalisation we won't have to check this maybe
// that way we only have to check the versions
// get correct name capitalization so that the logged output is correct
final library = ProjectName.ofString(info.name);

final versions = [for (v in info.versions) v.name];
Expand All @@ -313,8 +317,8 @@ class Installer {
a global scope, the new version is always set as the current one.
**/
public function installFromHaxelib(library:ProjectName, version:SemVer, forceSet:Bool = false) {
// TODO with stricter capitalisation we won't have to check this maybe
final info = Connection.getInfo(library);
// get correct name capitalization so that the logged output is correct
final library = ProjectName.ofString(info.name);

final versions = [for (v in info.versions) v.name];
Expand Down Expand Up @@ -344,6 +348,8 @@ class Installer {
public function installVcsLibrary(library:ProjectName, id:VcsID, vcsData:VcsData) {
installVcs(library, id, vcsData);

library = getName(library, id, vcsData.subDir);

scope.setVcsVersion(library, id, vcsData);

if (vcsData.subDir != null) {
Expand Down Expand Up @@ -452,6 +458,19 @@ class Installer {
}
}

/** Get the name found in the `haxelib.json` for a vcs library.
If `givenName` is an alias (it is completely different from the internal name)
then `givenName` is returned instead
**/
function getName(givenName:ProjectName, id:VcsID, subDir:Null<String>):ProjectName {
final jsonPath = scope.getPath(givenName, id) + (if (subDir != null) subDir else "") + Data.JSON;
if (!FileSystem.exists(jsonPath))
return givenName;
final internalName = Data.readData(File.getContent(jsonPath), false).name;
return ProjectName.getCorrectOrAlias(internalName, givenName);
}

function handleDependenciesGeneral(library:ProjectName, installData:InstallData) {
if (skipDependencies)
return;
Expand Down Expand Up @@ -499,23 +518,28 @@ class Installer {
final installData = getInstallData(libs);

for (lib in installData) {
final library = lib.name;
final version = lib.version;
userInterface.log('Installing dependency $library $version');
userInterface.log('Installing dependency ${lib.name} $version');

switch lib.installData {
case Haxelib(v) if (!forceInstallDependencies && repository.isVersionInstalled(library, v)):
userInterface.log('Library $library version $v is already installed');
case Haxelib(v) if (!forceInstallDependencies && repository.isVersionInstalled(lib.name, v)):
userInterface.log('Library ${lib.name} version $v is already installed');
continue;
default:
}

try
installFromInstallData(library, lib.installData)
installFromInstallData(lib.name, lib.installData)
catch (e) {
userInterface.log(e.toString());
continue;
}

final library = switch lib.installData {
case VcsInstall(version, vcsData): getName(lib.name, version, vcsData.subDir);
case _: lib.name;
}

// vcs versions always get set
if (!scope.isLibraryInstalled(library) || lib.installData.match(VcsInstall(_))) {
setVersionAndLog(library, lib.installData);
Expand Down Expand Up @@ -674,8 +698,7 @@ class Installer {

function installZip(library:ProjectName, version:SemVer, zip:List<haxe.zip.Entry>):Void {
userInterface.log('Installing $library...');
final rootPath = repository.getProjectPath(library);
FsUtils.safeDir(rootPath);

final versionPath = repository.getVersionPath(library, version);
FsUtils.safeDir(versionPath);

Expand Down Expand Up @@ -780,6 +803,7 @@ class Installer {
}
}
} else {
FsUtils.safeDir(libPath);
doVcsClone();
}

Expand Down
88 changes: 83 additions & 5 deletions src/haxelib/api/Repository.hx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@ class CurrentVersionException extends haxe.Exception {}
directly modifying them.
**/
class Repository {
/** Name of file used to keep track of current version. **/
static final CURRENT = ".current";

/** Name of file used to keep track of capitalization. **/
static final NAME = ".name";

/**
The path to the repository.
Expand Down Expand Up @@ -77,11 +81,14 @@ class Repository {
var libraryName:ProjectName;

for (dir in FileSystem.readDirectory(path)) {
// hidden or not a folder
if (dir.startsWith(".") || !FileSystem.isDirectory(Path.join([path, dir])))
// hidden, not a folder, or has upper case letters
if (dir.startsWith(".") || !FileSystem.isDirectory(Path.join([path, dir])) || dir.toLowerCase() != dir)
continue;

libraryName = try ProjectName.ofString(Data.unsafe(dir)) catch (_:haxe.Exception) continue;
libraryName = getCorrectProjectName(
try ProjectName.ofString(Data.unsafe(dir))
catch (_:haxe.Exception) continue
);

if (!isFilteredOut(libraryName))
projects.push(libraryName);
Expand Down Expand Up @@ -190,6 +197,9 @@ class Repository {
Set current version of project `name` to `version`.
Throws an error if `name` or `version` of `name` is not installed.
`name` may also be used as the official version of the library name
if installing a proper semver release
**/
public function setCurrentVersion(name:ProjectName, version:Version):Void {
if (!FileSystem.exists(getProjectRootPath(name)))
Expand All @@ -199,8 +209,15 @@ class Repository {
throw 'Library $name version $version is not installed';

final currentFilePath = getCurrentFilePath(name);
final isNewLibrary = !FileSystem.exists(currentFilePath);

File.saveContent(currentFilePath, version);

// if the library is being installed for the first time, or this is a proper version
// or it is a git/hg/dev version but there are no proper versions installed
// proper semver releases replace names given by git/hg/dev versions
if (isNewLibrary || SemVer.isValid(version) || !doesLibraryHaveOfficialVersion(name))
setCapitalizationIfNeeded(name);
}

/**
Expand Down Expand Up @@ -263,17 +280,73 @@ class Repository {
return getProjectVersionPath(name, version);
}

/**
Returns the correctly capitalized name for library `name`.
`name` can be any possible capitalization variation of the library name.
**/
public function getCorrectName(name:ProjectName):ProjectName {
return getCorrectProjectName(name);
}

static inline function doesNameHaveCapitals(name:ProjectName):Bool {
return name != ProjectName.ofString(name.toLowerCase());
}

function setCapitalizationIfNeeded(name:ProjectName):Void {
// if it is not all lowercase then we save the actual capitalisation in the `.name` file
final filePath = getNameFilePath(name);

if (doesNameHaveCapitals(name)) {
File.saveContent(filePath, name);
return;
}

try
FileSystem.deleteFile(filePath)
catch (_) {
// file didn't exist so we're good
}
}

// returns whether or not `name` has any versions installed which are not dev/git/hg
function doesLibraryHaveOfficialVersion(name:ProjectName):Bool {
final root = getProjectRootPath(name);

for (sub in FileSystem.readDirectory(root)) {
// ignore .dev and .current files
if (sub.startsWith("."))
continue;

final version = Data.unsafe(sub);
if (SemVer.isValid(version))
return true;
}
return false;
}

inline function getCurrentFilePath(name:ProjectName):String {
return addToRepoPath(name, CURRENT);
}

inline function getNameFilePath(name:ProjectName):String {
return addToRepoPath(name, NAME);
}

inline function getCurrentFileContent(name:ProjectName):String {
return try
File.getContent(getCurrentFilePath(name)).trim()
catch(e:haxe.Exception)
throw new CurrentVersionException('No current version set for library \'$name\'');
}

inline function getCorrectProjectName(name:ProjectName):ProjectName {
return try
ProjectName.ofString(File.getContent(getNameFilePath(name)).trim())
catch(e:haxe.Exception)
name;
}

inline function getProjectRootPath(name:ProjectName):String {
return addToRepoPath(name).addTrailingSlash();
}
Expand Down Expand Up @@ -309,8 +382,13 @@ class Repository {
public function setDevPath(name:ProjectName, path:String) {
final root = getProjectRootPath(name);

if (!FileSystem.exists(root))
FileSystem.createDirectory(root);
final isNew = !FileSystem.exists(root);

FileSystem.createDirectory(root);

if (isNew || !doesLibraryHaveOfficialVersion(name)) {
setCapitalizationIfNeeded(name);
}

final devFile = Path.join([root, DEV]);

Expand Down
5 changes: 4 additions & 1 deletion src/haxelib/api/ScriptRunner.hx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ typedef Dependencies = Array<Dependency>;
@:noDoc
/** Library data needed in order to run it **/
typedef LibraryRunData = {
/** This may be an alias. **/
name:ProjectName,
/** This is the actual name found in the `haxelib.json`. **/
internalName:ProjectName,
version:VersionOrDev,
dependencies:Dependencies,
main:String,
Expand Down Expand Up @@ -90,7 +93,7 @@ class ScriptRunner {
setState({
dir: library.path,
run: "1",
runName: library.name
runName: library.internalName
});

final output = Sys.command(cmd, args);
Expand Down
Loading

0 comments on commit 92ee0f4

Please sign in to comment.