3 * Subversion based package repository and package manager
4 * sk, cx, danstowell, LFSaw
6 * this is the interface class for
7 * accessing the SVN repository, (eg. the sourceforge quarks project)
8 * downloading those to the local quarks folder (Platform.userAppSupportDir/quarks)
9 * installing individual quarks by symlinking from the local quark folder into the [Platform dependent]/Extensions folder
16 classvar global, <allInstances, <known;
19 *global { ^(global ?? { global = this.new; }) }
21 *new { | reposPath, localPath |
23 if((this.known[reposPath].isNil.not) and: (this.known[reposPath] != localPath), {
24 ("The repository is in the list of known repositories, but with a different local path. You are recommended to use this local path:"
25 + this.known[reposPath]).warn;
27 newQ = super.new.initQuarks(
31 allInstances.add(newQ);
34 initQuarks{|reposPath, localPath|
35 local = LocalQuarks(localPath, this);
36 repos = QuarkSVNRepository(reposPath, local);
40 Class.initClassTree( Platform );
41 allInstances = List(1);
43 "https://quarks.svn.sourceforge.net/svnroot/quarks" -> (Platform.userAppSupportDir +/+ "quarks"),
44 "https://svn.sonenvir.at/svnroot/SonEnvir/trunk/src/quarks-sonenvir" -> (Platform.userAppSupportDir +/+ "quarks-sonenvir"),
45 "https://sc3-plugins.svn.sourceforge.net/svnroot/sc3-plugins/build" -> (Platform.userAppSupportDir +/+ "SC3plugins")
50 this.global; // ensure the global one is constructed
51 q = allInstances.detect({|q| q.repos.url == url});
52 if(q.isNil && this.known[url].isNil.not, {
53 q = Quarks.new(url, this.known[url]);
61 if((q = local.findQuark(name)).isNil,{
62 Error("Local Quark "+name+" not found, cannot update from repository").throw;
64 repos.svn("update", (local.path+/+q.path).escapeChar($ ));
71 // this should create or manage the directory file too if it exists
73 if((q = local.findQuark(name)).isNil,{
74 Error("Local Quark code not found, cannot add to repository").throw;
76 repos.svn("add",local.path++"/"++q.path);
78 commit { |name,message|
80 if((q = local.findQuark(name)).isNil,{
81 Error("Local Quark code not found, cannot commit to repository").throw;
84 Error("svn log message required to commit").throw;
86 repos.svn("commit","-m",message,"-F",local.path +/+ q.path);
88 // TODO: Deprecate "checkoutDirectory" - "updateDirectory" can be used whether or not there's an existing one
90 "Please wait for directory to be checked out.".postln;
98 this.checkedOut.do { |q| q.postDesc };
101 this.repos.quarks.do { |q| q.postDesc };
104 checkoutAll { repos.checkoutAll(local.path) }
105 checkout { |name, version, sync=false|
107 if(local.findQuark(name,version).notNil,{
108 inform("Quark "++name++" already checked out");
112 q = repos.findQuark(name,version);
114 Error("Quark not found in repository.").throw;
116 repos.checkout(q, local.path, sync);
118 // DEPRECATED because it has a different and confusing functionality w.r.t. QuarkSVNRepos.checkDir
121 "Quarks.checkDir is deprecated".warn;
122 d = (Platform.userExtensionDir +/+ local.name).escapeChar($ );
123 if(d.pathMatch.isEmpty,{
124 ("creating: " + d).inform;
125 ("mkdir -p" + d).systemCmd;
134 q = local.findQuark(name);
135 repos.svn("status",(local.path +/+ q.path).escapeChar($ ));
137 repos.svn("status",local.path.escapeChar($ ));
141 // of quarks in local, select those also present in userExtensionDir
142 ^local.quarks.select{|q|
143 (Platform.userExtensionDir.escapeChar($ )
149 install { | name , includeDependencies=true, checkoutIfNeeded=true |
150 var q, deps, installed, dirname, quarksForDep;
152 if(this.isInstalled(name),{
153 (name + "already installed").inform;
157 q = local.findQuark(name);
159 if(checkoutIfNeeded) {
160 (name.asString + " not found in local quarks; checking out from remote ...").postln;
161 this.checkout(name, sync: true);
162 q = local.reread.findQuark(name);
164 Error("Quark" + name + "install: checkout failed.").throw;
168 Error(name.asString + "not found in local quarks. Not yet downloaded from the repository ?").throw;
172 if(q.isCompatible.not,{
173 (q.name + " reports that it is not compatible with your current class library. See the help file for further information.").inform;
177 // create /quarks/ directory if needed
178 if(this.repos.checkDir.not){this.checkoutDirectory};
180 // Now ensure that the dependencies are installed (if available given the current active reposses)
181 if(includeDependencies, {
182 q.dependencies(true).do({ |dep|
183 quarksForDep = if(dep.repos.isNil, {this}, {Quarks.forUrl(dep.repos)});
184 if(quarksForDep.isNil, {
185 ("Quarks:install - unable to find repository for dependency '" ++ dep.name
186 ++ "' - you may need to satisfy this dependency manually. No repository detected locally with URL "++dep.repos).warn;
188 if(quarksForDep.isInstalled(dep.name).not, {
190 quarksForDep.install(dep.name, false, checkoutIfNeeded)
192 ("Unable to satisfy dependency of '"++name++"' on '"++dep.name
193 ++"' - you may need to install '"++dep.name++"' manually.").warn;
200 // Ensure the correct folder-hierarchy exists first
201 dirname = (Platform.userExtensionDir +/+ local.name +/+ q.path).dirname;
202 if(File.exists(dirname).not, {
203 ("mkdir -p " + dirname.escapeChar($ )).systemCmd;
206 // install via symlink to Extensions/<quarks-dir>
207 ("ln -s " + (local.path +/+ q.path).escapeChar($ ) + (Platform.userExtensionDir +/+ local.name +/+ q.path).escapeChar($ )).systemCmd;
208 (q.name + "installed").inform;
211 this.installed.do { |q| q.postDesc };
213 isInstalled { arg name;
214 ^this.installed.detect{|quark| quark.name == name }.notNil
217 var q, deps, installed;
218 name = name.asString;
219 if(this.isInstalled(name).not,{
223 q = local.findQuark(name);
227 "is not found in Local quarks in order to look up its relative path. You may remove the symlink manually."
231 // install via symlink to Extensions/Quarks
232 ("rm " + (Platform.userExtensionDir +/+ local.name +/+ q.path).escapeChar($ )).systemCmd;
233 (q.name + "uninstalled").inform;
237 var q, helpdoc, path;
239 q = local.findQuark(name);
243 "not found in local quarks. Not yet downloaded from the repository ?"
247 helpdoc = q.info.helpdoc;
250 ("No primary helpdoc listed for Quark"+name).inform;
252 path = Quarks.local.path.select{|c| (c != $\\)}
253 +/+ q.path +/+ helpdoc;
260 ///// convenience methods for Quarks.global
262 *checkoutAll { this.global.repos.checkoutAll(this.global.local.path) }
263 *checkout { | name, version, sync | this.global.checkout(name,version, sync); }
266 return Quark objects for each in App Support/SuperCollider/Quarks
267 (so actually this would list any Quarks that are in local development
268 and not yet checked in) */
269 *checkedOut { ^this.global.checkedOut }
271 this.checkedOut.do { |q| q.postDesc };
274 this.global.listAvailable
277 download/update only the quark specification files from remote repos
278 and not the quarks themselves */
280 ^this.global.repos.updateDirectory
283 ^this.global.checkoutDirectory
285 *update { |quarkName| this.global.update(quarkName) }
288 this symlinks from {App Support}/SuperCollider/Quarks to
289 {App Support}/SuperCollider/Extensions
290 it is then in the SC compile path */
291 *install { |name, includeDependencies=true, checkoutIfNeeded=true|
292 this.global.install(name, includeDependencies, checkoutIfNeeded)
296 return Quark objects for each installed */
297 *installed { ^this.global.installed }
298 *listInstalled { ^this.global.listInstalled }
299 *isInstalled { |name| ^this.global.isInstalled(name) }
301 removes the symlink */
302 *uninstall { |name| ^this.global.uninstall(name) }
305 add code in {App Support}/SuperCollider/Quarks to the remote repos
306 and also adds the quarks file in DIRECTORY */
307 //*add { |name| this.global.add(name) }
309 you may also use standard svn tools within {App Support}/SuperCollider/Quarks */
310 *commit { |name,message| this.global.commit(name,message) }
311 // post the SVN status
312 *status { |name| this.global.status(name) }
314 *local { ^this.global.local }
315 *repos { ^this.global.repos }
316 *help {|name| ^this.global.help(name) }
321 // a gui for Quarks. 2007 by LFSaw.de
323 var window, caption, explanation, views, resetButton, saveButton, warning,
324 scrollview, scrB, flowLayout, /* quarksflow, */ height, maxPerPage, nextButton, prevButton;
326 var pageStart = 0, fillPage = { |start|
327 scrollview.visible = false;
329 views.do({ |view| view.remove });
331 scrollview.decorator.reset;
332 views = quarks.collect{|quark|
333 var qView = QuarkView.new(scrollview, 500@20, quark,
334 this.installed.detect{|it| it == quark}.notNil);
335 scrollview.decorator.nextLine;
338 scrollview.visible = true;
342 // note, this doesn't actually contact svn
343 // it only reads the DIRECTORY entries you've already checked out
344 quarks = this.repos.quarks.copy
345 .sort({ |a, b| a.name < b.name });
347 scrB = GUI.window.screenBounds;
348 height = min(quarks.size * 25 + 120, scrB.height - 60);
350 window = GUI.window.new(this.name, Rect.aboutPoint( scrB.center, 250, height.div( 2 )));
351 flowLayout = FlowLayout( window.view.bounds );
352 window.view.decorator = flowLayout;
354 caption = GUI.staticText.new(window, Rect(20,15,400,30));
355 caption.font_( GUI.font.new( GUI.font.defaultSansFace, 24 ));
356 caption.string = this.name;
357 window.view.decorator.nextLine;
359 if ( quarks.size == 0 ){
360 GUI.button.new(window, Rect(0, 0, 229, 20))
361 .states_([["checkout Quarks DIRECTORY", Color.black, Color.gray(0.5)]])
362 .action_({ this.checkoutDirectory; });
364 GUI.button.new(window, Rect(0, 0, 229, 20))
365 .states_([["update Quarks DIRECTORY", Color.black, Color.gray(0.5)]])
366 .action_({ this.updateDirectory;});
369 GUI.button.new(window, Rect(0, 0, 200, 20))
370 .states_([["refresh Quarks listing", Color.black, Color.gray(0.5)]])
376 window.view.decorator.nextLine;
378 GUI.button.new(window, Rect(0, 0, 150, 20))
379 .states_([["browse all help", Color.black, Color.gray(0.5)]])
380 .action_({ Help(this.local.path).gui });
382 // add open directory button (open is only implemented in OS X)
383 (thisProcess.platform.name == \osx).if{
384 GUI.button.new(window, Rect(15,15,150,20)).states_([["open quark directory", Color.black, Color.gray(0.5)]]).action_{ arg butt;
385 "open %".format(this.local.path.escapeChar($ )).unixCmd;
389 resetButton = GUI.button.new(window, Rect(15,15,75,20));
390 resetButton.states = [
391 ["reset", Color.black, Color.gray(0.5)]
393 resetButton.action = { arg butt;
397 saveButton = GUI.button.new(window, Rect(15,15,75,20));
398 saveButton.states = [
399 ["save", Color.black, Color.blue(1, 0.5)]
401 saveButton.action = { arg butt;
403 warning.string = "Applying changes, please wait";
404 warning.background_(Color(1.0, 1.0, 0.9));
407 qView.toBeInstalled.if({
408 this.install(qView.quark.name);
411 qView.toBeDeinstalled.if({
412 this.uninstall(qView.quark.name);
416 warning.string = "Done. You should now recompile sclang";
417 warning.background_(Color(0.9, 1.0, 0.9));
421 window.view.decorator.nextLine;
422 explanation = GUI.staticText.new(window, Rect(20,15,500,20));
423 explanation.string = "\"+\" -> installed, \"-\" -> not installed, \"*\" -> marked to install, \"x\" -> marked to uninstall";
424 window.view.decorator.nextLine;
426 warning = GUI.staticText.new(window, Rect(20,15,400,30));
427 warning.font_( GUI.font.new( GUI.font.defaultSansFace, 18 ));
429 window.view.decorator.nextLine;
430 GUI.staticText.new( window, 492 @ 1 ).background_( Color.grey ); window.view.decorator.nextLine;
432 flowLayout.margin_( 0 @0 ).gap_( 0@0 );
433 scrollview = GUI.scrollView.new(window, 500 @ (height - 165))
435 .autohidesScrollers_(true);
436 scrollview.decorator = FlowLayout( Rect( 0, 0, 500, quarks.size * 25 + 20 ));
439 fillPage.(pageStart);