Browse Source

commit partial changes :
- semver ok
- keep site api intact (don't send SemVer over remoting)
- use api versioning with major.minor version (starting from 3.0)
- skip dependencies and prevent error when reading an old haxelib dir

Nicolas Cannasse 12 years ago
parent
commit
54a5195d78
4 changed files with 245 additions and 111 deletions
  1. 68 63
      std/tools/haxelib/Data.hx
  2. 115 47
      std/tools/haxelib/Main.hx
  3. 60 0
      std/tools/haxelib/SemVer.hx
  4. 2 1
      std/tools/haxelib/SiteApi.hx

+ 68 - 63
std/tools/haxelib/Data.hx

@@ -23,7 +23,7 @@ package tools.haxelib;
 import haxe.zip.Reader;
 import haxe.zip.Entry;
 
-import haxe.xml.Check;
+import haxe.Json;
 
 typedef UserInfos = {
 	var name : String;
@@ -49,12 +49,12 @@ typedef ProjectInfos = {
 	var tags : List<String>;
 }
 
-typedef XmlInfos = {
+typedef Infos = {
 	var project : String;
 	var website : String;
 	var desc : String;
 	var license : String;
-	var version : String;
+	var version : SemVer;
 	var versionComments : String;
 	var developers : List<String>;
 	var tags : List<String>;
@@ -63,33 +63,12 @@ typedef XmlInfos = {
 
 class Data {
 
-	public static var XML = "haxelib.xml";
+	public static var JSON = "haxelib.json";
 	public static var DOCXML = "haxedoc.xml";
 	public static var REPOSITORY = "files";
 	public static var alphanum = ~/^[A-Za-z0-9_.-]+$/;
 	static var LICENSES = ["GPL","LGPL","BSD","Public","MIT"];
 
-	static function requiredAttribute( x : Xml, name ) {
-		var v = x.get(name);
-		if( v == null )
-			throw "Missing required attribute '"+name+"' in node "+x.nodeName;
-		return v;
-	}
-
-	static function requiredNode( x : Xml, name ) {
-		var v = x.elementsNamed(name).next();
-		if( v == null )
-			throw "Missing required node '"+name+"' in node "+x.nodeName;
-		return v;
-	}
-
-	static function requiredText( x : Xml ) {
-		var v = x.firstChild();
-		if( v == null || (v.nodeType != Xml.PCData && v.nodeType != Xml.CData) )
-			throw "Missing required text in node "+x.nodeName;
-		return v.nodeValue;
-	}
-
 	public static function safe( name : String ) {
 		if( !alphanum.match(name) )
 			throw "Invalid parameter : "+name;
@@ -104,6 +83,15 @@ class Data {
 		return safe(lib)+"-"+safe(ver)+".zip";
 	}
 
+	public static function locateBasePath( zip : List<Entry> ) {
+		for( f in zip ) {
+			if( StringTools.endsWith(f.fileName,JSON) ) {
+				return f.fileName.substr(0,f.fileName.length - JSON.length);
+			}
+		}
+		throw "No "+JSON+" found";
+	}
+
 	public static function readDoc( zip : List<Entry> ) : String {
 		for( f in zip )
 			if( StringTools.endsWith(f.fileName,DOCXML) )
@@ -111,62 +99,79 @@ class Data {
 		return null;
 	}
 
-	public static function readInfos( zip : List<Entry>, check : Bool ) : XmlInfos {
-		var xmldata = null;
+	public static function readInfos( zip : List<Entry>, check : Bool ) : Infos {
+		var infodata = null;
 		for( f in zip )
-			if( StringTools.endsWith(f.fileName,XML) ) {
-				xmldata = Reader.unzip(f).toString();
+			if( StringTools.endsWith(f.fileName,JSON) ) {
+				infodata = Reader.unzip(f).toString();
 				break;
 			}
-		if( xmldata == null )
-			throw XML+" not found in package";
-		return readData(xmldata,check);
+		if( infodata == null )
+			throw JSON + " not found in package";
+		
+		return readData(infodata,check);
 	}
 
-	static function doCheck( doc : Xml ) {
-		var sname = Att("name",FReg(alphanum));
-		var schema = RNode(
-			"project",
-			[ sname, Att("url"), Att("license",FEnum(LICENSES)) ],
-			RList([
-				RMulti( RNode("user",[sname]), true ),
-				RMulti( RNode("tag",[Att("v",FReg(alphanum))]) ),
-				RNode("description",[],RData()),
-				RNode("version",[sname],RData()),
-				RMulti(	RNode("depends",[sname,Att("version",FReg(alphanum),"")]) ),
-			])
-		);
-		haxe.xml.Check.checkDocument(doc,schema);
+	static function doCheck( doc : Dynamic ) {
+		if( Lambda.indexOf(LICENSES, doc.license) == -1 )
+			throw "License must be one of the following: " + LICENSES;
+		switch Type.typeof(doc.contributors) {
+			case TNull: throw "At least one contributor must be included";
+			//case TClass(String): doc.contributors = [doc.contributors];
+			case TClass(Array):
+			default: throw 'invalid type for contributors';
+		}
+		switch Type.typeof(doc.tags) {
+			case TClass(Array), TNull:
+			default: throw 'tags must be defined as array';
+		}
+		switch Type.typeof(doc.dependencies) {
+			case TObject, TNull:
+			default: throw 'dependencies must be defined as object';
+		}
+		switch Type.typeof(doc.releasenote) {
+			case TClass(String):
+			case TNull: throw 'no releasenote specified';
+			default: throw 'releasenote should be string';
+		}
 	}
 
-	public static function readData( xmldata : String, check : Bool ) : XmlInfos {
-		var doc = Xml.parse(xmldata);
+	public static function readData( jsondata: String, check : Bool ) : Infos {
+		var doc = Json.parse(jsondata);
 		if( check )
 			doCheck(doc);
-		var p = new haxe.xml.Fast(doc).node.project;
-		var project = p.att.name;
+		var project:String = doc.name;
 		if( project.length < 3 )
 			throw "Project name must contain at least 3 characters";
 		var tags = new List();
-		for( t in p.nodes.tag )
-			tags.add(t.att.v.toLowerCase());
+		if( doc.tags != null) {
+			var tagsArray:Array<String> = doc.tags;
+			for( t in tagsArray )
+				tags.add(t);
+		}
 		var devs = new List();
-		for( d in p.nodes.user )
-			devs.add(d.att.name);
+		var developers:Array<String> = doc.contributors;
+		
+		for( d in developers )
+			devs.add(d);
 		var deps = new List();
-		for( d in p.nodes.depends )
-			deps.add({ project : d.att.name, version : if( d.has.version ) d.att.version else "" });
+		if( doc.dependencies != null ) {
+			for( d in Reflect.fields(doc.dependencies) ) {
+				deps.add({ project: d, version: Std.string(Reflect.field(doc.dependencies, d)) });
+			}
+		}
+		
 		return {
 			project : project,
-			website : p.att.url,
-			desc : p.node.description.innerData,
-			version : p.node.version.att.name,
-			versionComments : p.node.version.innerData,
-			license : p.att.license,
+			website : doc.url,
+			desc : doc.description,
+			version : SemVer.ofString(doc.version),
+			versionComments : doc.releasenote,
+			license : doc.license,
 			tags : tags,
 			developers : devs,
-			dependencies : deps,
-		}
+			dependencies : deps
+		};
 	}
 
 }

+ 115 - 47
std/tools/haxelib/Main.hx

@@ -20,7 +20,11 @@
  * DEALINGS IN THE SOFTWARE.
  */
 package tools.haxelib;
+
 import haxe.zip.Reader;
+import sys.io.File;
+import sys.io.Process;
+import haxe.ds.Option;
 
 enum Answer {
 	Yes;
@@ -112,13 +116,14 @@ class ProgressIn extends haxe.io.Input {
 
 class Main {
 
-	static var VERSION = 104;
+	static var VERSION = SemVer.ofString('3.0.0-rc.2');
 	static var REPNAME = "lib";
 	static var SERVER = {
 		host : "lib.haxe.org",
 		port : 80,
 		dir : "",
-		url : "index.n"
+		url : "index.n",
+		apiVersion : VERSION.major+"."+VERSION.minor,
 	};
 
 	var argcur : Int;
@@ -130,31 +135,34 @@ class Main {
 	function new() {
 		args = Sys.args();
 		commands = new List();
-		addCommand("install",install,"install a given library");
-		addCommand("list",list,"list all installed libraries",false);
-		addCommand("upgrade",upgrade,"upgrade all installed libraries");
-		addCommand("update",update,"update a single library");
-		addCommand("remove",remove,"remove a given library/version",false);
-		addCommand("set",set,"set the current version for a library",false);
-		addCommand("search",search,"list libraries matching a word");
-		addCommand("info",info,"list informations on a given library");
-		addCommand("user",user,"list informations on a given user");
-		addCommand("register",register,"register a new user");
-		addCommand("submit",submit,"submit or update a library package");
-		addCommand("setup",setup,"set the haxelib repository path",false);
-		addCommand("config",config,"print the repository path",false);
-		addCommand("path",path,"give paths to libraries",false);
-		addCommand("run",run,"run the specified library with parameters",false);
-		addCommand("local",local,"install the specified package locally",false);
-		addCommand("dev",dev,"set the development directory for a given library",false);
+		addCommand("install", install, "install a given library");
+		addCommand("list", list, "list all installed libraries", false);
+		addCommand("upgrade", upgrade, "upgrade all installed libraries");
+		addCommand("update", update, "update a single library");
+		addCommand("updateself", updateSelf, "update haxelib itself");
+		addCommand("remove", remove, "remove a given library/version", false);
+		addCommand("set", set, "set the current version for a library", false);
+		addCommand("search", search, "list libraries matching a word");
+		addCommand("info", info, "list informations on a given library");
+		addCommand("user", user, "list informations on a given user");
+		addCommand("register", register, "register a new user");
+		addCommand("submit", submit, "submit or update a library package");
+		addCommand("setup", setup, "set the haxelib repository path", false);
+		addCommand("config", config, "print the repository path", false);
+		addCommand("path", path, "give paths to libraries", false);
+		addCommand("run", run, "run the specified library with parameters", false);
+		addCommand("local", local, "install the specified package locally", false);
+		addCommand("dev", dev, "set the development directory for a given library", false);
 		addCommand("git", git, "uses git repository as library");
 		addCommand("proxy", proxy, "setup the Http proxy");
 		initSite();
 	}
+	
+	
 
 	function initSite() {
-		siteUrl = "http://"+SERVER.host+":"+SERVER.port+"/"+SERVER.dir;
-		site = new SiteProxy(haxe.remoting.HttpConnection.urlConnect(siteUrl+SERVER.url).api);
+		siteUrl = "http://" + SERVER.host + ":" + SERVER.port + "/" + SERVER.dir;
+		site = new SiteProxy(haxe.remoting.HttpConnection.urlConnect(siteUrl + "api/" + SERVER.apiVersion + "/" + SERVER.url).api);
 	}
 
 	function param( name, ?passwd ) {
@@ -195,9 +203,7 @@ class Main {
 	}
 
 	function usage() {
-		var vmin = Std.string(VERSION % 100);
-		var ver = Std.int(VERSION/100) + "." + if( vmin.length == 1 ) "0"+vmin else vmin;
-		print("Haxe Library Manager "+ver+" - (c)2006-2012 Haxe Foundation");
+		print("Haxe Library Manager " + VERSION + " - (c)2006-2013 Haxe Foundation");
 		print(" Usage : haxelib [command] [options]");
 		print(" Commands :");
 		for( c in commands )
@@ -358,10 +364,11 @@ class Main {
 		}
 
 		// check if this version already exists
+
 		var sinfos = try site.infos(infos.project) catch( _ : Dynamic ) null;
 		if( sinfos != null )
 			for( v in sinfos.versions )
-				if( v.name == infos.version && ask("You're about to overwrite existing version '"+v.name+"', please confirm") == No )
+				if( v.name == infos.version.toString() && ask("You're about to overwrite existing version '"+v.name+"', please confirm") == No )
 					throw "Aborted";
 
 		// query a submit id that will identify the file
@@ -431,36 +438,26 @@ class Main {
 		print("Downloading "+filename+"...");
 		h.customRequest(false,progress);
 
-		doInstallFile(filepath,setcurrent);
-		site.postInstall(project,version);
+		doInstallFile(filepath, setcurrent);
+		site.postInstall(project, version);
 	}
 
 	function doInstallFile(filepath,setcurrent,?nodelete) {
-
 		// read zip content
 		var f = sys.io.File.read(filepath,true);
 		var zip = Reader.readZip(f);
 		f.close();
 		var infos = Data.readInfos(zip,false);
-
 		// create directories
 		var pdir = getRepository() + Data.safe(infos.project);
 		safeDir(pdir);
 		pdir += "/";
-		var target = pdir + Data.safe(infos.version);
+		var target = pdir + Data.safe(infos.version.toString());
 		safeDir(target);
 		target += "/";
 
 		// locate haxelib.xml base path
-		var basepath = null;
-		for( f in zip ) {
-			if( StringTools.endsWith(f.fileName,Data.XML) ) {
-				basepath = f.fileName.substr(0,f.fileName.length - Data.XML.length);
-				break;
-			}
-		}
-		if( basepath == null )
-			throw "No "+Data.XML+" found";
+		var basepath = Data.locateBasePath(zip);
 
 		// unzip content
 		for( zipfile in zip ) {
@@ -491,7 +488,7 @@ class Main {
 
 		// set current version
 		if( setcurrent || !sys.FileSystem.exists(pdir+".current") ) {
-			sys.io.File.saveContent(pdir+".current",infos.version);
+			sys.io.File.saveContent(pdir + ".current", infos.version.toString());
 			print("  Current version is now "+infos.version);
 		}
 
@@ -680,13 +677,82 @@ class Main {
 				setCurrent(p, inf.curversion, true);
 		}
 	}
-
-	function update() {
-		var prj = param("Library");
+	function updateByName(prj:String) {
 		var state = { rep : getRepository(), prompt : false, updated : false };
 		doUpdate(prj,state);
-		if( !state.updated )
+		return state.updated;
+	}
+	function update() {
+		var prj = param('Library');
+		if (!updateByName(prj))
 			print(prj + " is up to date");
+	}	
+	
+	function updateSelf() {
+		function tryBuild() {
+			var p = new Process('haxe', ['-neko', 'test.n', '-lib', 'haxelib_client', '-main', 'tools.haxelib.Main', '--no-output']);
+			return 
+				if (p.exitCode() == 0) None;
+				else Some(p.stderr.readAll().toString());
+		}
+		if (!updateByName('haxelib_client'))
+			print("haxelib is up to date");
+		switch tryBuild() {
+			case None:
+				var haxepath = Sys.getEnv("HAXEPATH"),
+					win = Sys.systemName() == "Windows";
+					
+				if (haxepath == null) 
+					throw 'HAXEPATH environment variable not defined';
+				else 
+					haxepath += 
+						switch (haxepath.charAt(haxepath.length - 1)) {
+							case '/', '\\': '';
+							default: '/';
+						}
+				
+				if (win) {
+					var file = '$haxepath/haxelib.n';
+					var p = new Process(
+						'haxe', 
+						[
+							'-neko', file, 
+							'-lib', 'haxelib_client', 
+							'-main', 'tools.haxelib.Main', 
+						]
+					);
+					if (p.exitCode() == 0) {
+						var p = new Process('nekotools', ['boot', file]);
+						if (p.exitCode() != 0) 
+							throw 'Error booting haxelib :' + p.stderr.readAll().toString();
+					}
+					else throw 'Error rebuilding haxelib: ' + p.stderr.readAll().toString();
+				}
+				else {
+					var p = new Process('haxelib', ['path', 'haxelib_client']);
+					if (p.exitCode() == 0) {
+						var args = [];
+						for (arg in p.stdout.readAll().toString().split('\n')) {
+							arg = StringTools.trim(arg);
+							if (arg.charAt(0) == '-') 
+								args.push(arg);
+							else if (arg.length > 0) 
+								args.push('-cp "$arg"');
+						};
+						
+						var file = '$haxepath/haxelib.sh';
+						try File.saveContent(
+							file,
+							'#!\nhaxe --run -main tools.haxelib.Main '+args.join(' ')
+						)
+						catch (e:Dynamic) 
+							throw 'Error writing file $file. Please ensure you have write permissions. Error message ' + Std.string(e);
+					}
+					else throw p.stdout.readAll();
+				}
+			case Some(error):
+				throw 'Error compiling haxelib client: $error';
+		}
 	}
 
 	function deleteRec(dir) {
@@ -760,8 +826,10 @@ class Main {
 				throw "Library "+prj+" has two version included "+version+" and "+p.version;
 			}
 		l.add({ project : prj, version : version });
-		var xml = sys.io.File.getContent(vdir+"/haxelib.xml");
-		var inf = Data.readData(xml,false);
+		var json = try sys.io.File.getContent(vdir+"/"+Data.JSON) catch( e : Dynamic ) null;
+		if( json == null )
+			return; // ignore missing haxelib.json, assume no dependencies
+		var inf = Data.readData(json,false);
 		for( d in inf.dependencies )
 			checkRec(d.project,if( d.version == "" ) null else d.version,l);
 	}
@@ -952,7 +1020,7 @@ class Main {
 		var code = p.exitCode();
 		return { code:code, out: code == 0 ? p.stdout.readAll().toString() : p.stderr.readAll().toString() };
 	}
-	
+
 	function proxy() {
 		var rep = getRepository();
 		var host = param("Proxy host");

+ 60 - 0
std/tools/haxelib/SemVer.hx

@@ -0,0 +1,60 @@
+package tools.haxelib;
+
+using Std;
+
+enum Preview {
+	ALPHA;
+	BETA;
+	RC;
+	REV;
+}
+
+
+class SemVer {
+	public var major:Int;
+	public var minor:Int;	
+	public var patch:Int;
+	public var preview:Null<Preview>;
+	public var previewNum:Null<Int>;
+	public function new(major, minor, patch, ?preview, ?previewNum) {
+		this.major = major;
+		this.minor = minor;
+		this.patch = patch;
+		this.preview = preview;
+		this.previewNum = previewNum;
+	}
+	
+	public function toString():String {
+		var ret = '$major.$minor.$patch';
+		if (preview != null) {
+			ret += '-' + preview.getName().toLowerCase();
+			if (previewNum != null) 
+				ret += '.' + previewNum;
+		}
+		return ret;
+	}
+	static var parse = ~/^([0-9]+)\.([0-9]+)\.([0-9]+)(-(alpha|beta|rc|rev)(\.([0-9]+))?)?$/;
+	
+	static public function ofString(s:String):SemVer 
+		return
+			if (parse.match(s)) 
+				new SemVer(
+					parse.matched(1).parseInt(),
+					parse.matched(2).parseInt(),
+					parse.matched(3).parseInt(),
+					switch parse.matched(5) {
+						case 'alpha': ALPHA;
+						case 'beta': BETA;
+						case 'rc': RC;
+						case 'rev': REV;
+						case v if (v == null): null;
+						case v: throw 'unrecognized preview tag $v';
+					},
+					switch parse.matched(7) {
+						case v if (v == null): null;
+						case v: v.parseInt();
+					}
+				)
+			else 
+				throw '$s is not a valid version string (should be major.minor.patch[-(alpha|beta|rc|rev)[.version]]';
+}

+ 2 - 1
std/tools/haxelib/SiteApi.hx

@@ -203,8 +203,9 @@ class SiteApi {
 
 		// look for current version
 		var current = null;
+		var vstr = infos.version.toString();
 		for( v in Version.manager.search({ project : p.id }) )
-			if( v.name == infos.version ) {
+			if( v.name == vstr ) {
 				current = v;
 				break;
 			}